From 690a360fadc9fd4b99a9a39f1b2b824aaee85e2a Mon Sep 17 00:00:00 2001 From: Lucas Dupin Date: Thu, 1 Oct 2020 12:13:09 -0700 Subject: [PATCH] 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 --- core/java/android/view/SurfaceControl.java | 77 ++++++ core/java/android/view/ViewRootImpl.java | 39 ++- .../drawable/BackgroundBlurDrawable.java | 257 ++++++++++++++++++ core/jni/android_view_SurfaceControl.cpp | 40 +++ core/res/res/values/dimens.xml | 1 + core/res/res/values/symbols.xml | 1 + 6 files changed, 414 insertions(+), 1 deletion(-) create mode 100644 core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 9ea4c93b75c7d..d37edaa633a5f 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -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 + + "}"; + } + } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 14ba699933f04..0e8cd546a2ae3 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -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 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; } diff --git a/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java b/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java new file mode 100644 index 0000000000000..6ea9e665c009e --- /dev/null +++ b/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java @@ -0,0 +1,257 @@ +/* + * 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 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); + } + } + } +} diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 17ced9f78f258..f1ec85a8570fb 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -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(transactionObj); + SurfaceControl* const ctrl = reinterpret_cast(nativeObject); + + std::vector 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(blurRadius), + .cornerRadiusTL = cornerRadiusTL, + .cornerRadiusTR = cornerRadiusTR, + .cornerRadiusBL = cornerRadiusBL, + .cornerRadiusBR = cornerRadiusBR, + .alpha = alpha, + .left = static_cast(left), + .top = static_cast(top), + .right = static_cast(right), + .bottom = static_cast(bottom)}); + } + + transaction->setBlurRegions(ctrl, blurRegionVector); +} + static void nativeSetSize(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, jint w, jint h) { auto transaction = reinterpret_cast(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", diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 7eff3d7bcb06a..544be54eb7859 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -858,6 +858,7 @@ 0px 0px + 100dp 140dp diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index e4a4e20ee0bb3..dbb20f06c373d 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4078,6 +4078,7 @@ +