Merge "Fix synchronisation in BackgroundBlurDrawable" into sc-dev

This commit is contained in:
Galia Peycheva
2021-02-26 14:30:04 +00:00
committed by Android (Google) Code Review
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

@@ -3969,11 +3969,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;
}
@@ -3981,18 +3982,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]);
}
}