• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.policy;
18 
19 import android.graphics.Rect;
20 import android.graphics.drawable.ColorDrawable;
21 import android.graphics.drawable.Drawable;
22 import android.os.Looper;
23 import android.view.Choreographer;
24 import android.view.DisplayListCanvas;
25 import android.view.RenderNode;
26 import android.view.ThreadedRenderer;
27 
28 /**
29  * The thread which draws a fill in background while the app is resizing in areas where the app
30  * content draw is lagging behind the resize operation.
31  * It starts with the creation and it ends once someone calls destroy().
32  * Any size changes can be passed by a call to setTargetRect will passed to the thread and
33  * executed via the Choreographer.
34  * @hide
35  */
36 public class BackdropFrameRenderer extends Thread implements Choreographer.FrameCallback {
37 
38     private DecorView mDecorView;
39 
40     // This is containing the last requested size by a resize command. Note that this size might
41     // or might not have been applied to the output already.
42     private final Rect mTargetRect = new Rect();
43 
44     // The render nodes for the multi threaded renderer.
45     private ThreadedRenderer mRenderer;
46     private RenderNode mFrameAndBackdropNode;
47     private RenderNode mSystemBarBackgroundNode;
48 
49     private final Rect mOldTargetRect = new Rect();
50     private final Rect mNewTargetRect = new Rect();
51 
52     private Choreographer mChoreographer;
53 
54     // Cached size values from the last render for the case that the view hierarchy is gone
55     // during a configuration change.
56     private int mLastContentWidth;
57     private int mLastContentHeight;
58     private int mLastCaptionHeight;
59     private int mLastXOffset;
60     private int mLastYOffset;
61 
62     // Whether to report when next frame is drawn or not.
63     private boolean mReportNextDraw;
64 
65     private Drawable mCaptionBackgroundDrawable;
66     private Drawable mUserCaptionBackgroundDrawable;
67     private Drawable mResizingBackgroundDrawable;
68     private ColorDrawable mStatusBarColor;
69     private ColorDrawable mNavigationBarColor;
70     private boolean mOldFullscreen;
71     private boolean mFullscreen;
72     private final int mResizeMode;
73     private final Rect mOldSystemInsets = new Rect();
74     private final Rect mOldStableInsets = new Rect();
75     private final Rect mSystemInsets = new Rect();
76     private final Rect mStableInsets = new Rect();
77 
BackdropFrameRenderer(DecorView decorView, ThreadedRenderer renderer, Rect initialBounds, Drawable resizingBackgroundDrawable, Drawable captionBackgroundDrawable, Drawable userCaptionBackgroundDrawable, int statusBarColor, int navigationBarColor, boolean fullscreen, Rect systemInsets, Rect stableInsets, int resizeMode)78     public BackdropFrameRenderer(DecorView decorView, ThreadedRenderer renderer, Rect initialBounds,
79             Drawable resizingBackgroundDrawable, Drawable captionBackgroundDrawable,
80             Drawable userCaptionBackgroundDrawable, int statusBarColor, int navigationBarColor,
81             boolean fullscreen, Rect systemInsets, Rect stableInsets, int resizeMode) {
82         setName("ResizeFrame");
83 
84         mRenderer = renderer;
85         onResourcesLoaded(decorView, resizingBackgroundDrawable, captionBackgroundDrawable,
86                 userCaptionBackgroundDrawable, statusBarColor, navigationBarColor);
87 
88         // Create a render node for the content and frame backdrop
89         // which can be resized independently from the content.
90         mFrameAndBackdropNode = RenderNode.create("FrameAndBackdropNode", null);
91 
92         mRenderer.addRenderNode(mFrameAndBackdropNode, true);
93 
94         // Set the initial bounds and draw once so that we do not get a broken frame.
95         mTargetRect.set(initialBounds);
96         mFullscreen = fullscreen;
97         mOldFullscreen = fullscreen;
98         mSystemInsets.set(systemInsets);
99         mStableInsets.set(stableInsets);
100         mOldSystemInsets.set(systemInsets);
101         mOldStableInsets.set(stableInsets);
102         mResizeMode = resizeMode;
103 
104         // Kick off our draw thread.
105         start();
106     }
107 
onResourcesLoaded(DecorView decorView, Drawable resizingBackgroundDrawable, Drawable captionBackgroundDrawableDrawable, Drawable userCaptionBackgroundDrawable, int statusBarColor, int navigationBarColor)108     void onResourcesLoaded(DecorView decorView, Drawable resizingBackgroundDrawable,
109             Drawable captionBackgroundDrawableDrawable, Drawable userCaptionBackgroundDrawable,
110             int statusBarColor, int navigationBarColor) {
111         mDecorView = decorView;
112         mResizingBackgroundDrawable = resizingBackgroundDrawable != null
113                 ? resizingBackgroundDrawable.getConstantState().newDrawable()
114                 : null;
115         mCaptionBackgroundDrawable = captionBackgroundDrawableDrawable != null
116                 ? captionBackgroundDrawableDrawable.getConstantState().newDrawable()
117                 : null;
118         mUserCaptionBackgroundDrawable = userCaptionBackgroundDrawable != null
119                 ? userCaptionBackgroundDrawable.getConstantState().newDrawable()
120                 : null;
121         if (mCaptionBackgroundDrawable == null) {
122             mCaptionBackgroundDrawable = mResizingBackgroundDrawable;
123         }
124         if (statusBarColor != 0) {
125             mStatusBarColor = new ColorDrawable(statusBarColor);
126             addSystemBarNodeIfNeeded();
127         } else {
128             mStatusBarColor = null;
129         }
130         if (navigationBarColor != 0) {
131             mNavigationBarColor = new ColorDrawable(navigationBarColor);
132             addSystemBarNodeIfNeeded();
133         } else {
134             mNavigationBarColor = null;
135         }
136     }
137 
addSystemBarNodeIfNeeded()138     private void addSystemBarNodeIfNeeded() {
139         if (mSystemBarBackgroundNode != null) {
140             return;
141         }
142         mSystemBarBackgroundNode = RenderNode.create("SystemBarBackgroundNode", null);
143         mRenderer.addRenderNode(mSystemBarBackgroundNode, false);
144     }
145 
146     /**
147      * Call this function asynchronously when the window size has been changed or when the insets
148      * have changed or whether window switched between a fullscreen or non-fullscreen layout.
149      * The change will be picked up once per frame and the frame will be re-rendered accordingly.
150      *
151      * @param newTargetBounds The new target bounds.
152      * @param fullscreen Whether the window is currently drawing in fullscreen.
153      * @param systemInsets The current visible system insets for the window.
154      * @param stableInsets The stable insets for the window.
155      */
setTargetRect(Rect newTargetBounds, boolean fullscreen, Rect systemInsets, Rect stableInsets)156     public void setTargetRect(Rect newTargetBounds, boolean fullscreen, Rect systemInsets,
157             Rect stableInsets) {
158         synchronized (this) {
159             mFullscreen = fullscreen;
160             mTargetRect.set(newTargetBounds);
161             mSystemInsets.set(systemInsets);
162             mStableInsets.set(stableInsets);
163             // Notify of a bounds change.
164             pingRenderLocked(false /* drawImmediate */);
165         }
166     }
167 
168     /**
169      * The window got replaced due to a configuration change.
170      */
onConfigurationChange()171     public void onConfigurationChange() {
172         synchronized (this) {
173             if (mRenderer != null) {
174                 // Enforce a window redraw.
175                 mOldTargetRect.set(0, 0, 0, 0);
176                 pingRenderLocked(false /* drawImmediate */);
177             }
178         }
179     }
180 
181     /**
182      * All resources of the renderer will be released. This function can be called from the
183      * the UI thread as well as the renderer thread.
184      */
releaseRenderer()185     public void releaseRenderer() {
186         synchronized (this) {
187             if (mRenderer != null) {
188                 // Invalidate the current content bounds.
189                 mRenderer.setContentDrawBounds(0, 0, 0, 0);
190 
191                 // Remove the render node again
192                 // (see comment above - better to do that only once).
193                 mRenderer.removeRenderNode(mFrameAndBackdropNode);
194                 if (mSystemBarBackgroundNode != null) {
195                     mRenderer.removeRenderNode(mSystemBarBackgroundNode);
196                 }
197 
198                 mRenderer = null;
199 
200                 // Exit the renderer loop.
201                 pingRenderLocked(false /* drawImmediate */);
202             }
203         }
204     }
205 
206     @Override
run()207     public void run() {
208         try {
209             Looper.prepare();
210             synchronized (this) {
211                 mChoreographer = Choreographer.getInstance();
212             }
213             Looper.loop();
214         } finally {
215             releaseRenderer();
216         }
217         synchronized (this) {
218             // Make sure no more messages are being sent.
219             mChoreographer = null;
220             Choreographer.releaseInstance();
221         }
222     }
223 
224     /**
225      * The implementation of the FrameCallback.
226      * @param frameTimeNanos The time in nanoseconds when the frame started being rendered,
227      * in the {@link System#nanoTime()} timebase.  Divide this value by {@code 1000000}
228      */
229     @Override
doFrame(long frameTimeNanos)230     public void doFrame(long frameTimeNanos) {
231         synchronized (this) {
232             if (mRenderer == null) {
233                 reportDrawIfNeeded();
234                 // Tell the looper to stop. We are done.
235                 Looper.myLooper().quit();
236                 return;
237             }
238             doFrameUncheckedLocked();
239         }
240     }
241 
doFrameUncheckedLocked()242     private void doFrameUncheckedLocked() {
243         mNewTargetRect.set(mTargetRect);
244         if (!mNewTargetRect.equals(mOldTargetRect)
245                 || mOldFullscreen != mFullscreen
246                 || !mStableInsets.equals(mOldStableInsets)
247                 || !mSystemInsets.equals(mOldSystemInsets)
248                 || mReportNextDraw) {
249             mOldFullscreen = mFullscreen;
250             mOldTargetRect.set(mNewTargetRect);
251             mOldSystemInsets.set(mSystemInsets);
252             mOldStableInsets.set(mStableInsets);
253             redrawLocked(mNewTargetRect, mFullscreen, mSystemInsets, mStableInsets);
254         }
255     }
256 
257     /**
258      * The content is about to be drawn and we got the location of where it will be shown.
259      * If a "redrawLocked" call has already been processed, we will re-issue the call
260      * if the previous call was ignored since the size was unknown.
261      * @param xOffset The x offset where the content is drawn to.
262      * @param yOffset The y offset where the content is drawn to.
263      * @param xSize The width size of the content. This should not be 0.
264      * @param ySize The height of the content.
265      * @return true if a frame should be requested after the content is drawn; false otherwise.
266      */
onContentDrawn(int xOffset, int yOffset, int xSize, int ySize)267     public boolean onContentDrawn(int xOffset, int yOffset, int xSize, int ySize) {
268         synchronized (this) {
269             final boolean firstCall = mLastContentWidth == 0;
270             // The current content buffer is drawn here.
271             mLastContentWidth = xSize;
272             mLastContentHeight = ySize - mLastCaptionHeight;
273             mLastXOffset = xOffset;
274             mLastYOffset = yOffset;
275 
276             // Inform the renderer of the content's new bounds
277             mRenderer.setContentDrawBounds(
278                     mLastXOffset,
279                     mLastYOffset,
280                     mLastXOffset + mLastContentWidth,
281                     mLastYOffset + mLastCaptionHeight + mLastContentHeight);
282 
283             // If this was the first call and redrawLocked got already called prior
284             // to us, we should re-issue a redrawLocked now.
285             return firstCall
286                     && (mLastCaptionHeight != 0 || !mDecorView.isShowingCaption());
287         }
288     }
289 
onRequestDraw(boolean reportNextDraw)290     public void onRequestDraw(boolean reportNextDraw) {
291         synchronized (this) {
292             mReportNextDraw = reportNextDraw;
293             mOldTargetRect.set(0, 0, 0, 0);
294             pingRenderLocked(true /* drawImmediate */);
295         }
296     }
297 
298     /**
299      * Redraws the background, the caption and the system inset backgrounds if something changed.
300      *
301      * @param newBounds The window bounds which needs to be drawn.
302      * @param fullscreen Whether the window is currently drawing in fullscreen.
303      * @param systemInsets The current visible system insets for the window.
304      * @param stableInsets The stable insets for the window.
305      */
redrawLocked(Rect newBounds, boolean fullscreen, Rect systemInsets, Rect stableInsets)306     private void redrawLocked(Rect newBounds, boolean fullscreen, Rect systemInsets,
307             Rect stableInsets) {
308 
309         // While a configuration change is taking place the view hierarchy might become
310         // inaccessible. For that case we remember the previous metrics to avoid flashes.
311         // Note that even when there is no visible caption, the caption child will exist.
312         final int captionHeight = mDecorView.getCaptionHeight();
313 
314         // The caption height will probably never dynamically change while we are resizing.
315         // Once set to something other then 0 it should be kept that way.
316         if (captionHeight != 0) {
317             // Remember the height of the caption.
318             mLastCaptionHeight = captionHeight;
319         }
320 
321         // Make sure that the other thread has already prepared the render draw calls for the
322         // content. If any size is 0, we have to wait for it to be drawn first.
323         if ((mLastCaptionHeight == 0 && mDecorView.isShowingCaption()) ||
324                 mLastContentWidth == 0 || mLastContentHeight == 0) {
325             return;
326         }
327 
328         // Since the surface is spanning the entire screen, we have to add the start offset of
329         // the bounds to get to the surface location.
330         final int left = mLastXOffset + newBounds.left;
331         final int top = mLastYOffset + newBounds.top;
332         final int width = newBounds.width();
333         final int height = newBounds.height();
334 
335         mFrameAndBackdropNode.setLeftTopRightBottom(left, top, left + width, top + height);
336 
337         // Draw the caption and content backdrops in to our render node.
338         DisplayListCanvas canvas = mFrameAndBackdropNode.start(width, height);
339         final Drawable drawable = mUserCaptionBackgroundDrawable != null
340                 ? mUserCaptionBackgroundDrawable : mCaptionBackgroundDrawable;
341 
342         if (drawable != null) {
343             drawable.setBounds(0, 0, left + width, top + mLastCaptionHeight);
344             drawable.draw(canvas);
345         }
346 
347         // The backdrop: clear everything with the background. Clipping is done elsewhere.
348         if (mResizingBackgroundDrawable != null) {
349             mResizingBackgroundDrawable.setBounds(0, mLastCaptionHeight, left + width, top + height);
350             mResizingBackgroundDrawable.draw(canvas);
351         }
352         mFrameAndBackdropNode.end(canvas);
353 
354         drawColorViews(left, top, width, height, fullscreen, systemInsets, stableInsets);
355 
356         // We need to render the node explicitly
357         mRenderer.drawRenderNode(mFrameAndBackdropNode);
358 
359         reportDrawIfNeeded();
360     }
361 
drawColorViews(int left, int top, int width, int height, boolean fullscreen, Rect systemInsets, Rect stableInsets)362     private void drawColorViews(int left, int top, int width, int height,
363             boolean fullscreen, Rect systemInsets, Rect stableInsets) {
364         if (mSystemBarBackgroundNode == null) {
365             return;
366         }
367         DisplayListCanvas canvas = mSystemBarBackgroundNode.start(width, height);
368         mSystemBarBackgroundNode.setLeftTopRightBottom(left, top, left + width, top + height);
369         final int topInset = DecorView.getColorViewTopInset(mStableInsets.top, mSystemInsets.top);
370         final int bottomInset = DecorView.getColorViewBottomInset(stableInsets.bottom,
371                 systemInsets.bottom);
372         final int rightInset = DecorView.getColorViewRightInset(stableInsets.right,
373                 systemInsets.right);
374         final int leftInset = DecorView.getColorViewLeftInset(stableInsets.left,
375                 systemInsets.left);
376         if (mStatusBarColor != null) {
377             mStatusBarColor.setBounds(0, 0, left + width, topInset);
378             mStatusBarColor.draw(canvas);
379         }
380 
381         // We only want to draw the navigation bar if our window is currently fullscreen because we
382         // don't want the navigation bar background be moving around when resizing in docked mode.
383         // However, we need it for the transitions into/out of docked mode.
384         if (mNavigationBarColor != null && fullscreen) {
385             final int size = DecorView.getNavBarSize(bottomInset, rightInset, leftInset);
386             if (DecorView.isNavBarToRightEdge(bottomInset, rightInset)) {
387                 mNavigationBarColor.setBounds(width - size, 0, width, height);
388             } else if (DecorView.isNavBarToLeftEdge(bottomInset, leftInset)) {
389                 mNavigationBarColor.setBounds(0, 0, size, height);
390             } else {
391                 mNavigationBarColor.setBounds(0, height - size, width, height);
392             }
393             mNavigationBarColor.draw(canvas);
394         }
395         mSystemBarBackgroundNode.end(canvas);
396         mRenderer.drawRenderNode(mSystemBarBackgroundNode);
397     }
398 
399     /** Notify view root that a frame has been drawn by us, if it has requested so. */
reportDrawIfNeeded()400     private void reportDrawIfNeeded() {
401         if (mReportNextDraw) {
402             if (mDecorView.isAttachedToWindow()) {
403                 mDecorView.getViewRootImpl().reportDrawFinish();
404             }
405             mReportNextDraw = false;
406         }
407     }
408 
409     /**
410      * Sends a message to the renderer to wake up and perform the next action which can be
411      * either the next rendering or the self destruction if mRenderer is null.
412      * Note: This call must be synchronized.
413      *
414      * @param drawImmediate if we should draw immediately instead of scheduling a frame
415      */
pingRenderLocked(boolean drawImmediate)416     private void pingRenderLocked(boolean drawImmediate) {
417         if (mChoreographer != null && !drawImmediate) {
418             mChoreographer.postFrameCallback(this);
419         } else {
420             doFrameUncheckedLocked();
421         }
422     }
423 
setUserCaptionBackgroundDrawable(Drawable userCaptionBackgroundDrawable)424     void setUserCaptionBackgroundDrawable(Drawable userCaptionBackgroundDrawable) {
425         mUserCaptionBackgroundDrawable = userCaptionBackgroundDrawable;
426     }
427 }
428