Fix synchronisation in BackgroundBlurDrawable

The PositionUpdateListener in BackgroundBlurDrawable is called from
a thread pool to update the position of the BlurRegion. After that,
in frame drawing callback (RenderThread), the Aggregator takes all
the settings in BackgroundBlurDrawable and dispatches them to SF.
However, while the RenderThread is working on frame N, the Ui thread
is working on frame N+1. So when the frame drawing callback is
called on RenderThread for frame N, BackgroundBlurDrawable
might already be holding settings for frame N+1.

In this CL, we fix that by making BackgroundBlurDrawable work only on Ui
thread, while BlurRegion lives only on RenderThread. Just before
drawing, we copy the state of BackgroundBlurDrawable into a BlurRegion.
So when render thread starts doing its thing for frame N, Ui thread can
start setting up BackgroundBlurDrawable for frame N+1. The settings for
frame N are already copied over to BlurRegion. In frame drawing
callback, we take the blur regions if there is a pending update and send
them to SF.

Bug: 167166562
Test: m && atest BlurAggregatorTest
Change-Id: Icec0fa8e97de30b7bad15b97f848ae642076652f
This commit is contained in:
Galia Peycheva
2021-02-06 13:00:14 +01:00
parent 09f52aa440
commit d129f1e00e
4 changed files with 578 additions and 142 deletions

View File

@@ -3504,64 +3504,4 @@ 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
+ "}";
}
}
}

View File

@@ -3962,11 +3962,12 @@ public final class ViewRootImpl implements ViewParent,
}
private void addFrameCallbackIfNeeded() {
boolean nextDrawUseBlastSync = mNextDrawUseBlastSync;
boolean hasBlur = mBlurRegionAggregator.hasRegions();
boolean reportNextDraw = mReportNextDraw;
final boolean nextDrawUseBlastSync = mNextDrawUseBlastSync;
final boolean reportNextDraw = mReportNextDraw;
final boolean hasBlurUpdates = mBlurRegionAggregator.hasUpdates();
final boolean needsCallbackForBlur = hasBlurUpdates || mBlurRegionAggregator.hasRegions();
if (!nextDrawUseBlastSync && !reportNextDraw && !hasBlur) {
if (!nextDrawUseBlastSync && !reportNextDraw && !needsCallbackForBlur) {
return;
}
@@ -3974,18 +3975,22 @@ public final class ViewRootImpl implements ViewParent,
Log.d(mTag, "Creating frameDrawingCallback"
+ " nextDrawUseBlastSync=" + nextDrawUseBlastSync
+ " reportNextDraw=" + reportNextDraw
+ " hasBlur=" + hasBlur);
+ " hasBlurUpdates=" + hasBlurUpdates);
}
// The callback will run on a worker thread pool from the render thread.
final BackgroundBlurDrawable.BlurRegion[] blurRegionsForFrame =
needsCallbackForBlur ? mBlurRegionAggregator.getBlurRegionsCopyForRT() : null;
// The callback will run on the render thread.
HardwareRenderer.FrameDrawingCallback frameDrawingCallback = frame -> {
if (DEBUG_BLAST) {
Log.d(mTag, "Received frameDrawingCallback frameNum=" + frame + "."
+ " Creating transactionCompleteCallback=" + nextDrawUseBlastSync);
}
if (hasBlur) {
mBlurRegionAggregator.dispatchBlurTransactionIfNeeded(frame);
if (needsCallbackForBlur) {
mBlurRegionAggregator
.dispatchBlurTransactionIfNeeded(frame, blurRegionsForFrame, hasBlurUpdates);
}
if (mBlastBufferQueue == null) {

View File

@@ -19,6 +19,7 @@ package com.android.internal.graphics.drawable;
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiThread;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -31,12 +32,14 @@ import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RenderNode;
import android.graphics.drawable.Drawable;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.view.SurfaceControl;
import android.util.LongSparseArray;
import android.view.ViewRootImpl;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
/**
* A drawable that keeps track of a blur region, pokes a hole under it, and propagates its state
@@ -52,26 +55,40 @@ public final class BackgroundBlurDrawable extends Drawable {
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 =
private boolean mVisible = true;
// Confined to UiThread. The values are copied into a BlurRegion, which lives on
// RenderThread to avoid interference with UiThread updates.
private int mBlurRadius;
private float mCornerRadiusTL;
private float mCornerRadiusTR;
private float mCornerRadiusBL;
private float mCornerRadiusBR;
private float mAlpha = 1;
// Do not update from UiThread. This holds the latest position for this drawable. It is used
// by the Aggregator from RenderThread to get the final position of the blur region sent to SF
private final Rect mRect = new Rect();
// This is called from a thread pool. The callbacks might come out of order w.r.t. the frame
// number, so we send a Runnable holding the actual update to the Aggregator. The Aggregator
// can apply the update on RenderThread when processing that same frame.
@VisibleForTesting
public 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);
}
mAggregator.onRenderNodePositionChanged(frameNumber, () -> {
mRect.set(left, top, right, bottom);
});
}
@Override
public void positionLost(long frameNumber) {
synchronized (mAggregator) {
mBlurRegion.rect.setEmpty();
mAggregator.onBlurRegionUpdated(BackgroundBlurDrawable.this, mBlurRegion);
}
mAggregator.onRenderNodePositionChanged(frameNumber, () -> {
mRect.setEmpty();
});
}
};
@@ -79,6 +96,7 @@ public final class BackgroundBlurDrawable extends Drawable {
mAggregator = aggregator;
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
mPaint.setColor(Color.TRANSPARENT);
mPaint.setAntiAlias(true);
mRenderNode = new RenderNode("BackgroundBlurDrawable");
mRenderNode.addPositionUpdateListener(mPositionUpdateListener);
}
@@ -104,23 +122,30 @@ public final class BackgroundBlurDrawable extends Drawable {
public boolean setVisible(boolean visible, boolean restart) {
boolean changed = super.setVisible(visible, restart);
if (changed) {
mBlurRegion.visible = visible;
mVisible = visible;
mAggregator.onBlurDrawableUpdated(this);
}
return changed;
}
@Override
public void setAlpha(int alpha) {
mBlurRegion.alpha = alpha / 255f;
invalidateSelf();
if (mAlpha != alpha / 255f) {
mAlpha = alpha / 255f;
invalidateSelf();
mAggregator.onBlurDrawableUpdated(this);
}
}
/**
* Blur radius in pixels.
*/
public void setBlurRadius(int blurRadius) {
mBlurRegion.blurRadius = blurRadius;
invalidateSelf();
if (mBlurRadius != blurRadius) {
mBlurRadius = blurRadius;
invalidateSelf();
mAggregator.onBlurDrawableUpdated(this);
}
}
/**
@@ -139,14 +164,18 @@ public final class BackgroundBlurDrawable extends Drawable {
*/
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;
if (mCornerRadiusTL != cornerRadiusTL
|| mCornerRadiusTR != cornerRadiusTR
|| mCornerRadiusBL != cornerRadiusBL
|| mCornerRadiusBR != cornerRadiusBR) {
mCornerRadiusTL = cornerRadiusTL;
mCornerRadiusTR = cornerRadiusTR;
mCornerRadiusBL = cornerRadiusBL;
mCornerRadiusBR = cornerRadiusBR;
updatePath();
invalidateSelf();
mAggregator.onBlurDrawableUpdated(this);
}
updatePath();
invalidateSelf();
}
@Override
@@ -157,12 +186,10 @@ public final class BackgroundBlurDrawable extends Drawable {
}
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;
}
mTmpRadii[0] = mTmpRadii[1] = mCornerRadiusTL;
mTmpRadii[2] = mTmpRadii[3] = mCornerRadiusTR;
mTmpRadii[4] = mTmpRadii[5] = mCornerRadiusBL;
mTmpRadii[6] = mTmpRadii[7] = mCornerRadiusBR;
mRectPath.reset();
if (getAlpha() == 0 || !isVisible()) {
return;
@@ -182,17 +209,32 @@ public final class BackgroundBlurDrawable extends Drawable {
return PixelFormat.TRANSLUCENT;
}
@Override
public String toString() {
return "BackgroundBlurDrawable{"
+ "blurRadius=" + mBlurRadius
+ ", corners={" + mCornerRadiusTL
+ "," + mCornerRadiusTR
+ "," + mCornerRadiusBL
+ "," + mCornerRadiusBR
+ "}, alpha=" + mAlpha
+ ", visible=" + mVisible
+ "}";
}
/**
* 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 Object mRtLock = new Object();
// Maintains a list of all *visible* blur drawables. Confined to UI thread
private final ArraySet<BackgroundBlurDrawable> mDrawables = new ArraySet();
@GuardedBy("mRtLock")
private final LongSparseArray<ArraySet<Runnable>> mFrameRtUpdates = new LongSparseArray();
private final ViewRootImpl mViewRoot;
private float[][] mTmpBlurRegionsArray;
private boolean mNeedsUpdate;
private BlurRegion[] mTmpBlurRegionsForFrame = new BlurRegion[0];
private boolean mHasUiUpdates;
public Aggregator(ViewRootImpl viewRoot) {
mViewRoot = viewRoot;
@@ -209,60 +251,191 @@ public final class BackgroundBlurDrawable extends Drawable {
}
/**
* Called from RenderThread only, already locked.
* @param drawable
* @param blurRegion
* Called when a BackgroundBlurDrawable has been updated
*/
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);
@UiThread
void onBlurDrawableUpdated(BackgroundBlurDrawable drawable) {
final boolean shouldBeDrawn =
drawable.mAlpha != 0 && drawable.mBlurRadius > 0 && drawable.mVisible;
final boolean isDrawn = mDrawables.contains(drawable);
if (shouldBeDrawn) {
mHasUiUpdates = true;
if (!isDrawn) {
mDrawables.add(drawable);
if (DEBUG) {
Log.d(TAG, "Add " + drawable);
}
} else {
if (DEBUG) {
Log.d(TAG, "Update " + drawable);
}
}
} else {
mBlurRegions.put(drawable, blurRegion);
mNeedsUpdate = true;
} else if (!shouldBeDrawn && isDrawn) {
mHasUiUpdates = true;
mDrawables.remove(drawable);
if (DEBUG) {
Log.d(TAG, "Update " + blurRegion);
Log.d(TAG, "Remove " + drawable);
}
}
}
// Called from a thread pool
void onRenderNodePositionChanged(long frameNumber, Runnable update) {
// One of the blur region's position has changed, so we have to send an updated list
// of blur regions to SurfaceFlinger for this frame.
synchronized (mRtLock) {
ArraySet<Runnable> frameRtUpdates = mFrameRtUpdates.get(frameNumber);
if (frameRtUpdates == null) {
frameRtUpdates = new ArraySet<>();
mFrameRtUpdates.put(frameNumber, frameRtUpdates);
}
frameRtUpdates.add(update);
}
}
/**
* If there are any blur regions visible on the screen at the moment.
* @return true if there are any updates that need to be sent to SF
*/
@UiThread
public boolean hasUpdates() {
return mHasUiUpdates;
}
/**
* @return true if there are any visible blur regions
*/
@UiThread
public boolean hasRegions() {
return mBlurRegions.size() > 0;
return mDrawables.size() > 0;
}
/**
* Dispatch blur updates, if there were any.
* @param frameNumber Frame where the update should happen.
* @return an array of BlurRegions, which are holding a copy of the information in
* all the currently visible BackgroundBlurDrawables
*/
public void dispatchBlurTransactionIfNeeded(long frameNumber) {
synchronized (this) {
if (!mNeedsUpdate) {
return;
@UiThread
public BlurRegion[] getBlurRegionsCopyForRT() {
if (mHasUiUpdates) {
mTmpBlurRegionsForFrame = new BlurRegion[mDrawables.size()];
for (int i = 0; i < mDrawables.size(); i++) {
mTmpBlurRegionsForFrame[i] = new BlurRegion(mDrawables.valueAt(i));
}
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);
mHasUiUpdates = false;
}
return mTmpBlurRegionsForFrame;
}
/**
* Called on RenderThread.
*
* @return all blur regions if there are any ui or position updates for this frame,
* null otherwise
*/
@VisibleForTesting
public float[][] getBlurRegionsToDispatchToSf(long frameNumber,
BlurRegion[] blurRegionsForFrame, boolean hasUiUpdatesForFrame) {
synchronized (mRtLock) {
if (!hasUiUpdatesForFrame && (mFrameRtUpdates.size() == 0
|| mFrameRtUpdates.keyAt(0) > frameNumber)) {
return null;
}
// mFrameRtUpdates holds position updates coming from a thread pool span from
// RenderThread. At this point, all position updates for frame frameNumber should
// have been added to mFrameRtUpdates.
// Here, we apply all updates for frames <= frameNumber in case some previous update
// has been missed. This also protects mFrameRtUpdates from memory leaks.
while (mFrameRtUpdates.size() != 0 && mFrameRtUpdates.keyAt(0) <= frameNumber) {
final ArraySet<Runnable> frameUpdates = mFrameRtUpdates.valueAt(0);
mFrameRtUpdates.removeAt(0);
for (int i = 0; i < frameUpdates.size(); i++) {
frameUpdates.valueAt(i).run();
}
}
}
if (DEBUG) {
Log.d(TAG, "Dispatching " + blurRegionsForFrame.length + " blur regions:");
}
final float[][] blurRegionsArray = new float[blurRegionsForFrame.length][];
for (int i = 0; i < blurRegionsArray.length; i++) {
blurRegionsArray[i] = blurRegionsForFrame[i].toFloatArray();
if (DEBUG) {
Log.d(TAG, blurRegionsForFrame[i].toString());
}
}
return blurRegionsArray;
}
/**
* Called on RenderThread in FrameDrawingCallback.
* Dispatch all blur regions if there are any ui or position updates.
*/
public void dispatchBlurTransactionIfNeeded(long frameNumber,
BlurRegion[] blurRegionsForFrame, boolean hasUiUpdatesForFrame) {
final float[][] blurRegionsArray = getBlurRegionsToDispatchToSf(frameNumber,
blurRegionsForFrame, hasUiUpdatesForFrame);
if (blurRegionsArray != null) {
mViewRoot.dispatchBlurRegions(blurRegionsArray, frameNumber);
}
}
}
/**
* Wrapper for sending blur data to SurfaceFlinger
* Confined to RenderThread.
*/
public static final class BlurRegion {
public final int blurRadius;
public final float cornerRadiusTL;
public final float cornerRadiusTR;
public final float cornerRadiusBL;
public final float cornerRadiusBR;
public final float alpha;
public final Rect rect;
BlurRegion(BackgroundBlurDrawable drawable) {
alpha = drawable.mAlpha;
blurRadius = drawable.mBlurRadius;
cornerRadiusTL = drawable.mCornerRadiusTL;
cornerRadiusTR = drawable.mCornerRadiusTR;
cornerRadiusBL = drawable.mCornerRadiusBL;
cornerRadiusBR = drawable.mCornerRadiusBR;
rect = drawable.mRect;
}
/**
* Serializes this class into a float array that's more JNI friendly.
*/
float[] toFloatArray() {
final float[] floatArray = new float[10];
floatArray[0] = blurRadius;
floatArray[1] = alpha;
floatArray[2] = rect.left;
floatArray[3] = rect.top;
floatArray[4] = rect.right;
floatArray[5] = rect.bottom;
floatArray[6] = cornerRadiusTL;
floatArray[7] = cornerRadiusTR;
floatArray[8] = cornerRadiusBL;
floatArray[9] = cornerRadiusBR;
return floatArray;
}
@Override
public String toString() {
return "BlurRegion{"
+ "blurRadius=" + blurRadius
+ ", corners={" + cornerRadiusTL
+ "," + cornerRadiusTR
+ "," + cornerRadiusBL
+ "," + cornerRadiusBR
+ "}, alpha=" + alpha
+ ", rect=" + rect
+ "}";
}
}
}

View File

@@ -0,0 +1,318 @@
/*
* Copyright (C) 2021 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 android.view;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertNull;
import static junit.framework.TestCase.assertTrue;
import android.content.Context;
import android.platform.test.annotations.Presubmit;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
import com.android.internal.graphics.drawable.BackgroundBlurDrawable.Aggregator;
import com.android.internal.graphics.drawable.BackgroundBlurDrawable.BlurRegion;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@Presubmit
@RunWith(AndroidJUnit4.class)
public class BlurAggregatorTest {
private static final int TEST_BLUR_RADIUS = 30;
private static final int TEST_FRAME_NUMBER = 1;
private Context mContext;
private Aggregator mAggregator;
private BackgroundBlurDrawable mDrawable;
private ViewRootImpl mViewRoot;
@Before
public void setUp() {
mContext = getInstrumentation().getTargetContext();
getInstrumentation().runOnMainSync(() -> {
mViewRoot = new ViewRootImpl(mContext, mContext.getDisplayNoVerify());
});
mAggregator = new Aggregator(mViewRoot);
mDrawable = createTestBackgroundBlurDrawable();
}
private BackgroundBlurDrawable createTestBackgroundBlurDrawable() {
final BackgroundBlurDrawable drawable = mAggregator.createBackgroundBlurDrawable(mContext);
drawable.setBlurRadius(TEST_BLUR_RADIUS);
final boolean hasUpdates = mAggregator.hasUpdates();
final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
mAggregator.getBlurRegionsToDispatchToSf(TEST_FRAME_NUMBER, blurRegions, hasUpdates);
return drawable;
}
@Test
public void testBlurRadiusUpdatePropagatesToRenderThreadIfNeeded() {
mDrawable.setBlurRadius(TEST_BLUR_RADIUS);
assertFalse(mAggregator.hasUpdates());
mDrawable.setBlurRadius(0);
assertTrue(mAggregator.hasUpdates());
BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
assertEquals(0, blurRegions.length);
assertFalse(mAggregator.hasUpdates());
mDrawable.setBlurRadius(TEST_BLUR_RADIUS);
assertTrue(mAggregator.hasUpdates());
blurRegions = mAggregator.getBlurRegionsCopyForRT();
assertEquals(1, blurRegions.length);
assertEquals(TEST_BLUR_RADIUS, blurRegions[0].blurRadius);
assertFalse(mAggregator.hasUpdates());
}
@Test
public void testAlphaUpdatePropagatesToRenderThreadIfNeeded() {
mDrawable.setAlpha(20);
assertTrue(mAggregator.hasUpdates());
BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
assertEquals(1, blurRegions.length);
assertEquals(20 / 255f, blurRegions[0].alpha);
assertFalse(mAggregator.hasUpdates());
mDrawable.setAlpha(20);
assertFalse(mAggregator.hasUpdates());
mDrawable.setAlpha(0);
assertTrue(mAggregator.hasUpdates());
blurRegions = mAggregator.getBlurRegionsCopyForRT();
assertEquals(0, blurRegions.length);
assertFalse(mAggregator.hasUpdates());
}
@Test
public void testCornerRadiusUpdatePropagatesToRenderThreadIfNeeded() {
mDrawable.setCornerRadius(1f, 2f, 3f, 4f);
assertTrue(mAggregator.hasUpdates());
final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
assertEquals(1, blurRegions.length);
assertEquals(1f, blurRegions[0].cornerRadiusTL);
assertEquals(2f, blurRegions[0].cornerRadiusTR);
assertEquals(3f, blurRegions[0].cornerRadiusBL);
assertEquals(4f, blurRegions[0].cornerRadiusBR);
assertFalse(mAggregator.hasUpdates());
}
@Test
public void testVisibleUpdatePropagatesToRenderThreadIfNeeded() {
mDrawable.setVisible(false, /* restart= */false);
assertTrue(mAggregator.hasUpdates());
BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
assertEquals(0, blurRegions.length);
assertFalse(mAggregator.hasUpdates());
mDrawable.setVisible(true, /* restart= */ false);
assertTrue(mAggregator.hasUpdates());
blurRegions = mAggregator.getBlurRegionsCopyForRT();
assertEquals(1, blurRegions.length);
assertEquals(TEST_BLUR_RADIUS, blurRegions[0].blurRadius);
assertFalse(mAggregator.hasUpdates());
}
@Test
public void testBlurRegionCopyForRtIsSameIfNoUiUpdates() {
mDrawable.setBlurRadius(30);
BlurRegion[] blurRegions1 = mAggregator.getBlurRegionsCopyForRT();
assertEquals(1, blurRegions1.length);
assertEquals(30, blurRegions1[0].blurRadius);
BlurRegion[] blurRegions2 = mAggregator.getBlurRegionsCopyForRT();
assertEquals(blurRegions1, blurRegions2);
}
@Test
public void testPositionUpdateAppearsInBlurRegion() {
BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
assertEquals(1, blurRegions.length);
mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4);
mAggregator.getBlurRegionsToDispatchToSf(TEST_FRAME_NUMBER, blurRegions,
mAggregator.hasUpdates());
assertEquals(1, blurRegions[0].rect.left);
assertEquals(2, blurRegions[0].rect.top);
assertEquals(3, blurRegions[0].rect.right);
assertEquals(4, blurRegions[0].rect.bottom);
}
@Test
public void testNoBlurRegionsDispatchedWhenNoUpdates() {
final boolean hasUpdates = mAggregator.hasUpdates();
assertFalse(hasUpdates);
final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
assertEquals(1, blurRegions.length);
float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
TEST_FRAME_NUMBER, blurRegions, hasUpdates);
assertNull(blurRegionsForSf);
}
@Test
public void testBlurRegionDispatchedIfOnlyDrawableUpdated() {
mDrawable.setBlurRadius(50);
final boolean hasUpdates = mAggregator.hasUpdates();
assertTrue(hasUpdates);
final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
assertEquals(1, blurRegions.length);
float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
TEST_FRAME_NUMBER, blurRegions, hasUpdates);
assertNotNull(blurRegionsForSf);
assertEquals(1, blurRegionsForSf.length);
assertEquals(50f, blurRegionsForSf[0][0]);
}
@Test
public void testBlurRegionDispatchedIfOnlyPositionUpdated() {
final boolean hasUpdates = mAggregator.hasUpdates();
assertFalse(hasUpdates);
final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
assertEquals(1, blurRegions.length);
mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4);
float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
TEST_FRAME_NUMBER, blurRegions, hasUpdates);
assertNotNull(blurRegionsForSf);
assertEquals(1, blurRegionsForSf.length);
assertEquals((float) TEST_BLUR_RADIUS, blurRegionsForSf[0][0]);
assertEquals(1f, blurRegionsForSf[0][2]);
assertEquals(2f, blurRegionsForSf[0][3]);
assertEquals(3f, blurRegionsForSf[0][4]);
assertEquals(4f, blurRegionsForSf[0][5]);
}
@Test
public void testPositionUpdateIsAppliedInNextFrameIfMissed() {
final boolean hasUpdates = mAggregator.hasUpdates();
assertFalse(hasUpdates);
final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
assertEquals(1, blurRegions.length);
mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4);
float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
TEST_FRAME_NUMBER + 1, blurRegions, hasUpdates);
assertNotNull(blurRegionsForSf);
assertEquals(1, blurRegionsForSf.length);
assertEquals((float) TEST_BLUR_RADIUS, blurRegionsForSf[0][0]);
assertEquals(1f, blurRegionsForSf[0][2]);
assertEquals(2f, blurRegionsForSf[0][3]);
assertEquals(3f, blurRegionsForSf[0][4]);
assertEquals(4f, blurRegionsForSf[0][5]);
}
@Test
public void testMultipleDrawablesDispatchedToSfIfOneIsUpdated() {
final BackgroundBlurDrawable drawable2 = createTestBackgroundBlurDrawable();
drawable2.setBlurRadius(50);
final boolean hasUpdates = mAggregator.hasUpdates();
assertTrue(hasUpdates);
final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
assertEquals(2, blurRegions.length);
// Check that an update in one of the drawables triggers a dispatch of all blur regions
float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
TEST_FRAME_NUMBER, blurRegions, hasUpdates);
assertNotNull(blurRegionsForSf);
assertEquals(2, blurRegionsForSf.length);
// Check that the Aggregator deleted all position updates for frame TEST_FRAME_NUMBER
blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
TEST_FRAME_NUMBER, blurRegions, /* hasUiUpdates= */ false);
assertNull(blurRegionsForSf);
// Check that a position update triggers a dispatch of all blur regions
drawable2.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4);
blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
TEST_FRAME_NUMBER + 1, blurRegions, hasUpdates);
assertNotNull(blurRegionsForSf);
assertEquals(2, blurRegionsForSf.length);
}
@Test
public void testUiThreadUpdatesDoNotChangeStateOnRenderThread() {
// Updates for frame N
mDrawable.setBlurRadius(50);
mDrawable.setCornerRadius(1, 2, 3, 4);
mDrawable.setAlpha(20);
final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
assertEquals(1, blurRegions.length);
assertEquals(50, blurRegions[0].blurRadius);
assertEquals(20 / 255f, blurRegions[0].alpha);
assertEquals(1f, blurRegions[0].cornerRadiusTL);
assertEquals(2f, blurRegions[0].cornerRadiusTR);
assertEquals(3f, blurRegions[0].cornerRadiusBL);
assertEquals(4f, blurRegions[0].cornerRadiusBR);
// Updates for frame N+1
mDrawable.setBlurRadius(60);
mDrawable.setCornerRadius(10, 20, 30, 40);
mDrawable.setAlpha(40);
// Assert state for frame N is untouched
assertEquals(50, blurRegions[0].blurRadius);
assertEquals(20 / 255f, blurRegions[0].alpha);
assertEquals(1f, blurRegions[0].cornerRadiusTL);
assertEquals(2f, blurRegions[0].cornerRadiusTR);
assertEquals(3f, blurRegions[0].cornerRadiusBL);
assertEquals(4f, blurRegions[0].cornerRadiusBR);
}
@Test
public void testPositionUpdatesForFutureFramesAreNotAppliedForCurrentFrame() {
final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT();
mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4);
mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER + 1, 5, 6, 7, 8);
final float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf(
TEST_FRAME_NUMBER, blurRegions, /* hasUiUpdates= */ false);
assertNotNull(blurRegionsForSf);
assertEquals(1, blurRegionsForSf.length);
// Assert state for first frame is not affected by update for second frame
assertEquals((float) TEST_BLUR_RADIUS, blurRegionsForSf[0][0]);
assertEquals(1f, blurRegionsForSf[0][2]);
assertEquals(2f, blurRegionsForSf[0][3]);
assertEquals(3f, blurRegionsForSf[0][4]);
assertEquals(4f, blurRegionsForSf[0][5]);
final float[][] blurRegionsForSfForNextFrame = mAggregator.getBlurRegionsToDispatchToSf(
TEST_FRAME_NUMBER + 1, blurRegions, /* hasUiUpdates= */ false);
assertNotNull(blurRegionsForSfForNextFrame);
assertEquals(1, blurRegionsForSfForNextFrame.length);
// Assert second frame updates are applied normally
assertEquals((float) TEST_BLUR_RADIUS, blurRegionsForSfForNextFrame[0][0]);
assertEquals(5f, blurRegionsForSfForNextFrame[0][2]);
assertEquals(6f, blurRegionsForSfForNextFrame[0][3]);
assertEquals(7f, blurRegionsForSfForNextFrame[0][4]);
assertEquals(8f, blurRegionsForSfForNextFrame[0][5]);
}
}