Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 690a360f authored by Lucas Dupin's avatar Lucas Dupin
Browse files

Create cross window blur drawable

This introduces BackgroundBlurDrawable that does cross-window blurs
The drawable should not be created manually, it should be retrieved
from the ViewRootImpl

Test: manual
Bug: 159712515
Bug: 171916625
Change-Id: I697e93fc95ba3d0a7211b235b1c5d48a1968b939
parent 38af94c8
Loading
Loading
Loading
Loading
+77 −0
Original line number Diff line number Diff line
@@ -135,6 +135,8 @@ public final class SurfaceControl implements Parcelable {
            int blurRadius);
    private static native void nativeSetLayerStack(long transactionObj, long nativeObject,
            int layerStack);
    private static native void nativeSetBlurRegions(long transactionObj, long nativeObj,
            float[][] regions, int length);

    private static native boolean nativeClearContentFrameStats(long nativeObject);
    private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats);
@@ -2953,6 +2955,21 @@ public final class SurfaceControl implements Parcelable {
            return this;
        }

        /**
         * Specify what regions should be blurred on the {@link SurfaceControl}.
         *
         * @param sc SurfaceControl.
         * @param regions List of regions that will have blurs.
         * @return itself.
         * @see BlurRegion#toFloatArray()
         * @hide
         */
        public Transaction setBlurRegions(SurfaceControl sc, float[][] regions) {
            checkPreconditions(sc);
            nativeSetBlurRegions(mNativeObject, sc.mNativeObject, regions, regions.length);
            return this;
        }

        /**
         * @hide
         */
@@ -3482,4 +3499,64 @@ public final class SurfaceControl implements Parcelable {
    public static Transaction getGlobalTransaction() {
        return sGlobalTransaction;
    }

    /**
     * Wrapper for sending blur data to SurfaceFlinger.
     * @hide
     */
    public static final class BlurRegion {
        public int blurRadius;
        public float cornerRadiusTL;
        public float cornerRadiusTR;
        public float cornerRadiusBL;
        public float cornerRadiusBR;
        public float alpha = 1;
        public boolean visible = true;
        public final Rect rect = new Rect();

        private final float[] mFloatArray = new float[10];

        public BlurRegion() {
        }

        public BlurRegion(BlurRegion other) {
            rect.set(other.rect);
            blurRadius = other.blurRadius;
            alpha = other.alpha;
            cornerRadiusTL = other.cornerRadiusTL;
            cornerRadiusTR = other.cornerRadiusTR;
            cornerRadiusBL = other.cornerRadiusBL;
            cornerRadiusBR = other.cornerRadiusBR;
        }

        /**
         * Serializes this class into a float array that's more JNI friendly.
         */
        public float[] toFloatArray() {
            mFloatArray[0] = blurRadius;
            mFloatArray[1] = alpha;
            mFloatArray[2] = rect.left;
            mFloatArray[3] = rect.top;
            mFloatArray[4] = rect.right;
            mFloatArray[5] = rect.bottom;
            mFloatArray[6] = cornerRadiusTL;
            mFloatArray[7] = cornerRadiusTR;
            mFloatArray[8] = cornerRadiusBL;
            mFloatArray[9] = cornerRadiusBR;
            return mFloatArray;
        }

        @Override
        public String toString() {
            return "BlurRegion{"
                    + "blurRadius=" + blurRadius
                    + ", corners={" + cornerRadiusTL
                    + "," + cornerRadiusTR
                    + "," + cornerRadiusBL
                    + "," + cornerRadiusBR
                    + "}, alpha=" + alpha
                    + ", rect=" + rect
                    + "}";
        }
    }
}
+38 −1
Original line number Diff line number Diff line
@@ -187,6 +187,7 @@ import android.window.ClientWindowFrames;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
import com.android.internal.inputmethod.InputMethodDebug;
import com.android.internal.os.IResultReceiver;
import com.android.internal.os.SomeArgs;
@@ -654,6 +655,9 @@ public final class ViewRootImpl implements ViewParent,

    private ScrollCaptureConnection mScrollCaptureConnection;

    private final BackgroundBlurDrawable.Aggregator mBlurRegionAggregator =
            new BackgroundBlurDrawable.Aggregator(this);

    /**
     * @return {@link ImeFocusController} for this instance.
     */
@@ -3819,6 +3823,8 @@ public final class ViewRootImpl implements ViewParent,
    private HardwareRenderer.FrameCompleteCallback createFrameCompleteCallback(Handler handler,
            boolean reportNextDraw, ArrayList<Runnable> commitCallbacks) {
        return frameNr -> {
            mBlurRegionAggregator.dispatchBlurTransactionIfNeeded(frameNr);

            // Use a new transaction here since mRtBLASTSyncTransaction can only be accessed by
            // the render thread and mSurfaceChangedTransaction can only be accessed by the UI
            // thread. The temporary transaction is used so mRtBLASTSyncTransaction can be merged
@@ -3849,7 +3855,8 @@ public final class ViewRootImpl implements ViewParent,
                .captureFrameCommitCallbacks();
        final boolean needFrameCompleteCallback =
                mNextDrawUseBLASTSyncTransaction || mReportNextDraw
                        || (commitCallbacks != null && commitCallbacks.size() > 0);
                        || (commitCallbacks != null && commitCallbacks.size() > 0)
                        || mBlurRegionAggregator.hasRegions();
        if (needFrameCompleteCallback) {
            mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(
                    createFrameCompleteCallback(mAttachInfo.mHandler, mReportNextDraw,
@@ -9915,6 +9922,36 @@ public final class ViewRootImpl implements ViewParent,
        }
    }

    /**
     * Sends a list of blur regions to SurfaceFlinger, tagged with a frame.
     *
     * @param regionCopy List of regions
     * @param frameNumber Frame where it should be applied (or current when using BLAST)
     */
    public void dispatchBlurRegions(float[][] regionCopy, long frameNumber) {
        final SurfaceControl surfaceControl = getSurfaceControl();
        if (!surfaceControl.isValid()) {
            return;
        }
        if (useBLAST()) {
            synchronized (getBlastTransactionLock()) {
                getBLASTSyncTransaction().setBlurRegions(surfaceControl, regionCopy);
            }
        } else {
            SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
            transaction.setBlurRegions(surfaceControl, regionCopy);
            transaction.deferTransactionUntil(surfaceControl, getSurfaceControl(), frameNumber);
            transaction.apply();
        }
    }

    /**
     * Creates a background blur drawable for the backing {@link Surface}.
     */
    public BackgroundBlurDrawable createBackgroundBlurDrawable() {
        return mBlurRegionAggregator.createBackgroundBlurDrawable(mContext);
    }

    SurfaceControl.Transaction getBLASTSyncTransaction() {
        return mRtBLASTSyncTransaction;
    }
+257 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.
 */

package com.android.internal.graphics.drawable;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RenderNode;
import android.graphics.drawable.Drawable;
import android.util.ArrayMap;
import android.util.Log;
import android.view.SurfaceControl;
import android.view.ViewRootImpl;

import com.android.internal.R;

/**
 * A drawable that keeps track of a blur region, pokes a hole under it, and propagates its state
 * to SurfaceFlinger.
 */
public final class BackgroundBlurDrawable extends Drawable {

    private static final String TAG = BackgroundBlurDrawable.class.getSimpleName();
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    private final Aggregator mAggregator;
    private final RenderNode mRenderNode;
    private final Paint mPaint = new Paint();
    private final Path mRectPath = new Path();
    private final float[] mTmpRadii = new float[8];
    private final SurfaceControl.BlurRegion mBlurRegion = new SurfaceControl.BlurRegion();

    // This will be called from a thread pool.
    private final RenderNode.PositionUpdateListener mPositionUpdateListener =
            new RenderNode.PositionUpdateListener() {
            @Override
            public void positionChanged(long frameNumber, int left, int top, int right,
                    int bottom) {
                synchronized (mAggregator) {
                    mBlurRegion.rect.set(left, top, right, bottom);
                    mAggregator.onBlurRegionUpdated(BackgroundBlurDrawable.this, mBlurRegion);
                }
            }

            @Override
            public void positionLost(long frameNumber) {
                synchronized (mAggregator) {
                    mBlurRegion.rect.setEmpty();
                    mAggregator.onBlurRegionUpdated(BackgroundBlurDrawable.this, mBlurRegion);
                }
            }
        };

    private BackgroundBlurDrawable(Aggregator aggregator) {
        mAggregator = aggregator;
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
        mRenderNode = new RenderNode("BackgroundBlurDrawable");
        mRenderNode.addPositionUpdateListener(mPositionUpdateListener);
    }

    @Override
    public void draw(@NonNull Canvas canvas) {
        if (mRectPath.isEmpty() || !isVisible() || getAlpha() == 0) {
            return;
        }
        canvas.drawPath(mRectPath, mPaint);
        canvas.drawRenderNode(mRenderNode);
    }

    @Override
    public boolean setVisible(boolean visible, boolean restart) {
        boolean changed = super.setVisible(visible, restart);
        if (changed) {
            mBlurRegion.visible = visible;
        }
        return changed;
    }

    @Override
    public void setAlpha(int alpha) {
        mBlurRegion.alpha = alpha / 255f;
        invalidateSelf();
    }

    /**
     * Blur radius in pixels.
     */
    public void setBlurRadius(int blurRadius) {
        mBlurRegion.blurRadius = blurRadius;
        invalidateSelf();
    }

    /**
     * Sets the corner radius, in degrees.
     */
    public void setCornerRadius(float cornerRadius) {
        setCornerRadius(cornerRadius, cornerRadius, cornerRadius, cornerRadius);
    }

    /**
     * Sets the corner radius in degrees.
     * @param cornerRadiusTL top left radius.
     * @param cornerRadiusTR top right radius.
     * @param cornerRadiusBL bottom left radius.
     * @param cornerRadiusBR bottom right radius.
     */
    public void setCornerRadius(float cornerRadiusTL, float cornerRadiusTR, float cornerRadiusBL,
            float cornerRadiusBR) {
        synchronized (mAggregator) {
            mBlurRegion.cornerRadiusTL = cornerRadiusTL;
            mBlurRegion.cornerRadiusTR = cornerRadiusTR;
            mBlurRegion.cornerRadiusBL = cornerRadiusBL;
            mBlurRegion.cornerRadiusBR = cornerRadiusBR;
        }
        updatePath();
        invalidateSelf();
    }

    @Override
    public void setBounds(int left, int top, int right, int bottom) {
        super.setBounds(left, top, right, bottom);
        mRenderNode.setPosition(left, top, right, bottom);
        updatePath();
    }

    private void updatePath() {
        synchronized (mAggregator) {
            mTmpRadii[0] = mTmpRadii[1] = mBlurRegion.cornerRadiusTL;
            mTmpRadii[2] = mTmpRadii[3] = mBlurRegion.cornerRadiusTR;
            mTmpRadii[4] = mTmpRadii[5] = mBlurRegion.cornerRadiusBL;
            mTmpRadii[6] = mTmpRadii[7] = mBlurRegion.cornerRadiusBR;
        }
        mRectPath.reset();
        if (getAlpha() == 0 || !isVisible()) {
            return;
        }
        Rect bounds = getBounds();
        mRectPath.addRoundRect(bounds.left, bounds.top, bounds.right, bounds.bottom, mTmpRadii,
                Path.Direction.CW);
    }

    @Override
    public void setColorFilter(@Nullable ColorFilter colorFilter) {
        throw new IllegalArgumentException("not implemented");
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

    /**
     * Responsible for keeping track of all blur regions of a {@link ViewRootImpl} and posting a
     * message when it's time to propagate them.
     */
    public static final class Aggregator {

        private final ArrayMap<BackgroundBlurDrawable, SurfaceControl.BlurRegion> mBlurRegions =
                new ArrayMap<>();
        private final ViewRootImpl mViewRoot;
        private float[][] mTmpBlurRegionsArray;
        private boolean mNeedsUpdate;

        public Aggregator(ViewRootImpl viewRoot) {
            mViewRoot = viewRoot;
        }

        /**
         * Creates a blur region with default radius.
         */
        public BackgroundBlurDrawable createBackgroundBlurDrawable(Context context) {
            BackgroundBlurDrawable drawable = new BackgroundBlurDrawable(this);
            drawable.setBlurRadius(context.getResources().getDimensionPixelSize(
                    R.dimen.default_background_blur_radius));
            return drawable;
        }

        /**
         * Called from RenderThread only, already locked.
         * @param drawable
         * @param blurRegion
         */
        void onBlurRegionUpdated(BackgroundBlurDrawable drawable,
                SurfaceControl.BlurRegion blurRegion) {
            if (blurRegion.rect.isEmpty() || blurRegion.alpha == 0 || blurRegion.blurRadius == 0
                    || !blurRegion.visible) {
                mBlurRegions.remove(drawable);
                mNeedsUpdate = true;
                if (DEBUG) {
                    Log.d(TAG, "Remove " + blurRegion);
                }
            } else {
                mBlurRegions.put(drawable, blurRegion);
                mNeedsUpdate = true;
                if (DEBUG) {
                    Log.d(TAG, "Update " + blurRegion);
                }
            }
        }

        /**
         * If there are any blur regions visible on the screen at the moment.
         */
        public boolean hasRegions() {
            return mBlurRegions.size() > 0;
        }

        /**
         * Dispatch blur updates, if there were any.
         * @param frameNumber Frame where the update should happen.
         */
        public void dispatchBlurTransactionIfNeeded(long frameNumber) {
            synchronized (this) {
                if (!mNeedsUpdate) {
                    return;
                }
                mNeedsUpdate = false;

                if (mTmpBlurRegionsArray == null
                        || mTmpBlurRegionsArray.length != mBlurRegions.size()) {
                    mTmpBlurRegionsArray = new float[mBlurRegions.size()][];
                }
                if (DEBUG) {
                    Log.d(TAG, "onBlurRegionUpdated will dispatch " + mTmpBlurRegionsArray.length
                            + " regions for frame " + frameNumber);
                }
                for (int i = 0; i < mTmpBlurRegionsArray.length; i++) {
                    mTmpBlurRegionsArray[i] = mBlurRegions.valueAt(i).toFloatArray();
                }

                mViewRoot.dispatchBlurRegions(mTmpBlurRegionsArray, frameNumber);
            }
        }
    }
}
+40 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@
#include <private/gui/ComposerService.h>
#include <stdio.h>
#include <system/graphics.h>
#include <ui/BlurRegion.h>
#include <ui/ConfigStoreTypes.h>
#include <ui/DeviceProductInfo.h>
#include <ui/DisplayConfig.h>
@@ -522,6 +523,43 @@ static void nativeSetGeometry(JNIEnv* env, jclass clazz, jlong transactionObj, j
    transaction->setGeometry(ctrl, source, dst, orientation);
}

static void nativeSetBlurRegions(JNIEnv* env, jclass clazz, jlong transactionObj,
                                 jlong nativeObject, jobjectArray regions, jint regionsLength) {
    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);

    std::vector<BlurRegion> blurRegionVector;
    const int size = regionsLength;
    float region[10];
    for (int i = 0; i < size; i++) {
        jfloatArray regionArray = (jfloatArray)env->GetObjectArrayElement(regions, i);
        env->GetFloatArrayRegion(regionArray, 0, 10, region);
        float blurRadius = region[0];
        float alpha = region[1];
        float left = region[2];
        float top = region[3];
        float right = region[4];
        float bottom = region[5];
        float cornerRadiusTL = region[6];
        float cornerRadiusTR = region[7];
        float cornerRadiusBL = region[8];
        float cornerRadiusBR = region[9];

        blurRegionVector.push_back(BlurRegion{.blurRadius = static_cast<uint32_t>(blurRadius),
                                              .cornerRadiusTL = cornerRadiusTL,
                                              .cornerRadiusTR = cornerRadiusTR,
                                              .cornerRadiusBL = cornerRadiusBL,
                                              .cornerRadiusBR = cornerRadiusBR,
                                              .alpha = alpha,
                                              .left = static_cast<int>(left),
                                              .top = static_cast<int>(top),
                                              .right = static_cast<int>(right),
                                              .bottom = static_cast<int>(bottom)});
    }

    transaction->setBlurRegions(ctrl, blurRegionVector);
}

static void nativeSetSize(JNIEnv* env, jclass clazz, jlong transactionObj,
        jlong nativeObject, jint w, jint h) {
    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -1626,6 +1664,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = {
            (void*)nativeSetBackgroundBlurRadius },
    {"nativeSetLayerStack", "(JJI)V",
            (void*)nativeSetLayerStack },
    {"nativeSetBlurRegions", "(JJ[[FI)V",
            (void*)nativeSetBlurRegions },
    {"nativeSetShadowRadius", "(JJF)V",
            (void*)nativeSetShadowRadius },
    {"nativeSetFrameRate", "(JJFI)V",
+1 −0
Original line number Diff line number Diff line
@@ -858,6 +858,7 @@
    <dimen name="waterfall_display_right_edge_size">0px</dimen>
    <dimen name="waterfall_display_bottom_edge_size">0px</dimen>

    <dimen name="default_background_blur_radius">100dp</dimen>
    <!-- The maximum height of a thumbnail in a ThumbnailTemplate. The image will be reduced to that height in case they are bigger. -->
    <dimen name="controls_thumbnail_image_max_height">140dp</dimen>
    <!-- The maximum width of a thumbnail in a ThumbnailTemplate. The image will be reduced to that width in case they are bigger.-->
Loading