• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.graphics.drawable;
18 
19 import android.annotation.ColorInt;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.UiThread;
23 import android.content.Context;
24 import android.graphics.Canvas;
25 import android.graphics.Color;
26 import android.graphics.ColorFilter;
27 import android.graphics.Paint;
28 import android.graphics.Path;
29 import android.graphics.PixelFormat;
30 import android.graphics.PorterDuff;
31 import android.graphics.PorterDuffXfermode;
32 import android.graphics.Rect;
33 import android.graphics.RenderNode;
34 import android.graphics.drawable.Drawable;
35 import android.util.ArraySet;
36 import android.util.Log;
37 import android.util.LongSparseArray;
38 import android.view.ViewRootImpl;
39 
40 import com.android.internal.R;
41 import com.android.internal.annotations.GuardedBy;
42 import com.android.internal.annotations.VisibleForTesting;
43 
44 /**
45  * A drawable that keeps track of a blur region, pokes a hole under it, and propagates its state
46  * to SurfaceFlinger.
47  */
48 public final class BackgroundBlurDrawable extends Drawable {
49 
50     private static final String TAG = BackgroundBlurDrawable.class.getSimpleName();
51     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
52 
53     private final Aggregator mAggregator;
54     private final RenderNode mRenderNode;
55     private final Paint mPaint = new Paint();
56     private final Path mRectPath = new Path();
57     private final float[] mTmpRadii = new float[8];
58 
59     private boolean mVisible = true;
60 
61     // Confined to UiThread. The values are copied into a BlurRegion, which lives on
62     // RenderThread to avoid interference with UiThread updates.
63     private int mBlurRadius;
64     private float mCornerRadiusTL;
65     private float mCornerRadiusTR;
66     private float mCornerRadiusBL;
67     private float mCornerRadiusBR;
68     private float mAlpha = 1;
69 
70     // Do not update from UiThread. This holds the latest position for this drawable. It is used
71     // by the Aggregator from RenderThread to get the final position of the blur region sent to SF
72     private final Rect mRect = new Rect();
73     // This is called from a thread pool. The callbacks might come out of order w.r.t. the frame
74     // number, so we send a Runnable holding the actual update to the Aggregator. The Aggregator
75     // can apply the update on RenderThread when processing that same frame.
76     @VisibleForTesting
77     public final RenderNode.PositionUpdateListener mPositionUpdateListener =
78             new RenderNode.PositionUpdateListener() {
79             @Override
80             public void positionChanged(long frameNumber, int left, int top, int right,
81                     int bottom) {
82                 mAggregator.onRenderNodePositionChanged(frameNumber, () -> {
83                     mRect.set(left, top, right, bottom);
84                 });
85             }
86 
87             @Override
88             public void positionLost(long frameNumber) {
89                 mAggregator.onRenderNodePositionChanged(frameNumber, () -> {
90                     mRect.setEmpty();
91                 });
92             }
93         };
94 
BackgroundBlurDrawable(Aggregator aggregator)95     private BackgroundBlurDrawable(Aggregator aggregator) {
96         mAggregator = aggregator;
97         mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
98         mPaint.setColor(Color.TRANSPARENT);
99         mPaint.setAntiAlias(true);
100         mRenderNode = new RenderNode("BackgroundBlurDrawable");
101         mRenderNode.addPositionUpdateListener(mPositionUpdateListener);
102     }
103 
104     @Override
draw(@onNull Canvas canvas)105     public void draw(@NonNull Canvas canvas) {
106         if (mRectPath.isEmpty() || !isVisible() || getAlpha() == 0) {
107             return;
108         }
109 
110         canvas.drawPath(mRectPath, mPaint);
111         canvas.drawRenderNode(mRenderNode);
112     }
113 
114     /**
115      * Color that will be alpha blended on top of the blur.
116      */
setColor(@olorInt int color)117     public void setColor(@ColorInt int color) {
118         mPaint.setColor(color);
119     }
120 
121     @Override
setVisible(boolean visible, boolean restart)122     public boolean setVisible(boolean visible, boolean restart) {
123         boolean changed = super.setVisible(visible, restart);
124         if (changed) {
125             mVisible = visible;
126             mAggregator.onBlurDrawableUpdated(this);
127         }
128         return changed;
129     }
130 
131     @Override
setAlpha(int alpha)132     public void setAlpha(int alpha) {
133         if (mAlpha != alpha / 255f) {
134             mAlpha = alpha / 255f;
135             invalidateSelf();
136             mAggregator.onBlurDrawableUpdated(this);
137         }
138     }
139 
140     /**
141      * Blur radius in pixels.
142      */
setBlurRadius(int blurRadius)143     public void setBlurRadius(int blurRadius) {
144         if (mBlurRadius != blurRadius) {
145             mBlurRadius = blurRadius;
146             invalidateSelf();
147             mAggregator.onBlurDrawableUpdated(this);
148         }
149     }
150 
151     /**
152      * Sets the corner radius, in degrees.
153      */
setCornerRadius(float cornerRadius)154     public void setCornerRadius(float cornerRadius) {
155         setCornerRadius(cornerRadius, cornerRadius, cornerRadius, cornerRadius);
156     }
157 
158     /**
159      * Sets the corner radius in degrees.
160      * @param cornerRadiusTL top left radius.
161      * @param cornerRadiusTR top right radius.
162      * @param cornerRadiusBL bottom left radius.
163      * @param cornerRadiusBR bottom right radius.
164      */
setCornerRadius(float cornerRadiusTL, float cornerRadiusTR, float cornerRadiusBL, float cornerRadiusBR)165     public void setCornerRadius(float cornerRadiusTL, float cornerRadiusTR, float cornerRadiusBL,
166             float cornerRadiusBR) {
167         if (mCornerRadiusTL != cornerRadiusTL
168                 || mCornerRadiusTR != cornerRadiusTR
169                 || mCornerRadiusBL != cornerRadiusBL
170                 || mCornerRadiusBR != cornerRadiusBR) {
171             mCornerRadiusTL = cornerRadiusTL;
172             mCornerRadiusTR = cornerRadiusTR;
173             mCornerRadiusBL = cornerRadiusBL;
174             mCornerRadiusBR = cornerRadiusBR;
175             updatePath();
176             invalidateSelf();
177             mAggregator.onBlurDrawableUpdated(this);
178         }
179     }
180 
181     @Override
setBounds(int left, int top, int right, int bottom)182     public void setBounds(int left, int top, int right, int bottom) {
183         super.setBounds(left, top, right, bottom);
184         mRenderNode.setPosition(left, top, right, bottom);
185         updatePath();
186     }
187 
updatePath()188     private void updatePath() {
189         mTmpRadii[0] = mTmpRadii[1] = mCornerRadiusTL;
190         mTmpRadii[2] = mTmpRadii[3] = mCornerRadiusTR;
191         mTmpRadii[4] = mTmpRadii[5] = mCornerRadiusBL;
192         mTmpRadii[6] = mTmpRadii[7] = mCornerRadiusBR;
193         mRectPath.reset();
194         if (getAlpha() == 0 || !isVisible()) {
195             return;
196         }
197         Rect bounds = getBounds();
198         mRectPath.addRoundRect(bounds.left, bounds.top, bounds.right, bounds.bottom, mTmpRadii,
199                 Path.Direction.CW);
200     }
201 
202     @Override
setColorFilter(@ullable ColorFilter colorFilter)203     public void setColorFilter(@Nullable ColorFilter colorFilter) {
204         throw new IllegalArgumentException("not implemented");
205     }
206 
207     @Override
getOpacity()208     public int getOpacity() {
209         return PixelFormat.TRANSLUCENT;
210     }
211 
212     @Override
toString()213     public String toString() {
214         return "BackgroundBlurDrawable{"
215             + "blurRadius=" + mBlurRadius
216             + ", corners={" + mCornerRadiusTL
217             + "," + mCornerRadiusTR
218             + "," + mCornerRadiusBL
219             + "," + mCornerRadiusBR
220             + "}, alpha=" + mAlpha
221             + ", visible=" + mVisible
222             + "}";
223     }
224 
225     /**
226      * Responsible for keeping track of all blur regions of a {@link ViewRootImpl} and posting a
227      * message when it's time to propagate them.
228      */
229     public static final class Aggregator {
230         private final Object mRtLock = new Object();
231         // Maintains a list of all *visible* blur drawables. Confined to  UI thread
232         private final ArraySet<BackgroundBlurDrawable> mDrawables = new ArraySet();
233         @GuardedBy("mRtLock")
234         private final LongSparseArray<ArraySet<Runnable>> mFrameRtUpdates = new LongSparseArray();
235         private final ViewRootImpl mViewRoot;
236         private BlurRegion[] mTmpBlurRegionsForFrame = new BlurRegion[0];
237         private boolean mHasUiUpdates;
238 
Aggregator(ViewRootImpl viewRoot)239         public Aggregator(ViewRootImpl viewRoot) {
240             mViewRoot = viewRoot;
241         }
242 
243         /**
244          * Creates a blur region with default radius.
245          */
createBackgroundBlurDrawable(Context context)246         public BackgroundBlurDrawable createBackgroundBlurDrawable(Context context) {
247             BackgroundBlurDrawable drawable = new BackgroundBlurDrawable(this);
248             drawable.setBlurRadius(context.getResources().getDimensionPixelSize(
249                     R.dimen.default_background_blur_radius));
250             return drawable;
251         }
252 
253         /**
254          * Called when a BackgroundBlurDrawable has been updated
255          */
256         @UiThread
onBlurDrawableUpdated(BackgroundBlurDrawable drawable)257         void onBlurDrawableUpdated(BackgroundBlurDrawable drawable) {
258             final boolean shouldBeDrawn =
259                     drawable.mAlpha != 0 && drawable.mBlurRadius > 0 && drawable.mVisible;
260             final boolean isDrawn = mDrawables.contains(drawable);
261             if (shouldBeDrawn) {
262                 mHasUiUpdates = true;
263                 if (!isDrawn) {
264                     mDrawables.add(drawable);
265                     if (DEBUG) {
266                         Log.d(TAG, "Add " + drawable);
267                     }
268                 } else {
269                     if (DEBUG) {
270                         Log.d(TAG, "Update " + drawable);
271                     }
272                 }
273             } else if (!shouldBeDrawn && isDrawn) {
274                 mHasUiUpdates = true;
275                 mDrawables.remove(drawable);
276                 if (DEBUG) {
277                     Log.d(TAG, "Remove " + drawable);
278                 }
279             }
280         }
281 
282         // Called from a thread pool
onRenderNodePositionChanged(long frameNumber, Runnable update)283         void onRenderNodePositionChanged(long frameNumber, Runnable update) {
284             // One of the blur region's position has changed, so we have to send an updated list
285             // of blur regions to SurfaceFlinger for this frame.
286             synchronized (mRtLock) {
287                 ArraySet<Runnable> frameRtUpdates = mFrameRtUpdates.get(frameNumber);
288                 if (frameRtUpdates == null) {
289                     frameRtUpdates = new ArraySet<>();
290                     mFrameRtUpdates.put(frameNumber, frameRtUpdates);
291                 }
292                 frameRtUpdates.add(update);
293             }
294         }
295 
296         /**
297          * @return true if there are any updates that need to be sent to SF
298          */
299         @UiThread
hasUpdates()300         public boolean hasUpdates() {
301             return mHasUiUpdates;
302         }
303 
304         /**
305          * @return true if there are any visible blur regions
306          */
307         @UiThread
hasRegions()308         public boolean hasRegions() {
309             return mDrawables.size() > 0;
310         }
311 
312         /**
313          * @return an array of BlurRegions, which are holding a copy of the information in
314          *         all the currently visible BackgroundBlurDrawables
315          */
316         @UiThread
getBlurRegionsCopyForRT()317         public BlurRegion[] getBlurRegionsCopyForRT() {
318             if (mHasUiUpdates) {
319                 mTmpBlurRegionsForFrame = new BlurRegion[mDrawables.size()];
320                 for (int i = 0; i < mDrawables.size(); i++) {
321                     mTmpBlurRegionsForFrame[i] = new BlurRegion(mDrawables.valueAt(i));
322                 }
323                 mHasUiUpdates = false;
324             }
325 
326             return mTmpBlurRegionsForFrame;
327         }
328 
329         /**
330          * Called on RenderThread.
331          *
332          * @return all blur regions if there are any ui or position updates for this frame,
333          *         null otherwise
334          */
335         @VisibleForTesting
getBlurRegionsToDispatchToSf(long frameNumber, BlurRegion[] blurRegionsForFrame, boolean hasUiUpdatesForFrame)336         public float[][] getBlurRegionsToDispatchToSf(long frameNumber,
337                 BlurRegion[] blurRegionsForFrame, boolean hasUiUpdatesForFrame) {
338             synchronized (mRtLock) {
339                 if (!hasUiUpdatesForFrame && (mFrameRtUpdates.size() == 0
340                             || mFrameRtUpdates.keyAt(0) > frameNumber)) {
341                     return null;
342                 }
343 
344                 // mFrameRtUpdates holds position updates coming from a thread pool span from
345                 // RenderThread. At this point, all position updates for frame frameNumber should
346                 // have been added to mFrameRtUpdates.
347                 // Here, we apply all updates for frames <= frameNumber in case some previous update
348                 // has been missed. This also protects mFrameRtUpdates from memory leaks.
349                 while (mFrameRtUpdates.size() != 0 && mFrameRtUpdates.keyAt(0) <= frameNumber) {
350                     final ArraySet<Runnable> frameUpdates = mFrameRtUpdates.valueAt(0);
351                     mFrameRtUpdates.removeAt(0);
352                     for (int i = 0; i < frameUpdates.size(); i++) {
353                         frameUpdates.valueAt(i).run();
354                     }
355                 }
356             }
357 
358             if (DEBUG) {
359                 Log.d(TAG, "Dispatching " + blurRegionsForFrame.length + " blur regions:");
360             }
361 
362             final float[][] blurRegionsArray = new float[blurRegionsForFrame.length][];
363             for (int i = 0; i < blurRegionsArray.length; i++) {
364                 blurRegionsArray[i] = blurRegionsForFrame[i].toFloatArray();
365                 if (DEBUG) {
366                     Log.d(TAG, blurRegionsForFrame[i].toString());
367                 }
368             }
369             return blurRegionsArray;
370         }
371 
372         /**
373          * Called on RenderThread in FrameDrawingCallback.
374          * Dispatch all blur regions if there are any ui or position updates.
375          */
dispatchBlurTransactionIfNeeded(long frameNumber, BlurRegion[] blurRegionsForFrame, boolean hasUiUpdatesForFrame)376         public void dispatchBlurTransactionIfNeeded(long frameNumber,
377                 BlurRegion[] blurRegionsForFrame, boolean hasUiUpdatesForFrame) {
378             final float[][] blurRegionsArray = getBlurRegionsToDispatchToSf(frameNumber,
379                     blurRegionsForFrame, hasUiUpdatesForFrame);
380             if (blurRegionsArray != null) {
381                 mViewRoot.dispatchBlurRegions(blurRegionsArray, frameNumber);
382             }
383         }
384 
385     }
386 
387     /**
388      * Wrapper for sending blur data to SurfaceFlinger
389      * Confined to RenderThread.
390      */
391     public static final class BlurRegion {
392         public final int blurRadius;
393         public final float cornerRadiusTL;
394         public final float cornerRadiusTR;
395         public final float cornerRadiusBL;
396         public final float cornerRadiusBR;
397         public final float alpha;
398         public final Rect rect;
399 
BlurRegion(BackgroundBlurDrawable drawable)400         BlurRegion(BackgroundBlurDrawable drawable) {
401             alpha = drawable.mAlpha;
402             blurRadius = drawable.mBlurRadius;
403             cornerRadiusTL = drawable.mCornerRadiusTL;
404             cornerRadiusTR = drawable.mCornerRadiusTR;
405             cornerRadiusBL = drawable.mCornerRadiusBL;
406             cornerRadiusBR = drawable.mCornerRadiusBR;
407             rect = drawable.mRect;
408         }
409 
410         /**
411          * Serializes this class into a float array that's more JNI friendly.
412          */
toFloatArray()413         float[] toFloatArray() {
414             final float[] floatArray = new float[10];
415             floatArray[0] = blurRadius;
416             floatArray[1] = alpha;
417             floatArray[2] = rect.left;
418             floatArray[3] = rect.top;
419             floatArray[4] = rect.right;
420             floatArray[5] = rect.bottom;
421             floatArray[6] = cornerRadiusTL;
422             floatArray[7] = cornerRadiusTR;
423             floatArray[8] = cornerRadiusBL;
424             floatArray[9] = cornerRadiusBR;
425             return floatArray;
426         }
427 
428         @Override
toString()429         public String toString() {
430             return "BlurRegion{"
431                     + "blurRadius=" + blurRadius
432                     + ", corners={" + cornerRadiusTL
433                     + "," + cornerRadiusTR
434                     + "," + cornerRadiusBL
435                     + "," + cornerRadiusBR
436                     + "}, alpha=" + alpha
437                     + ", rect=" + rect
438                     + "}";
439         }
440     }
441 }
442