BackdropFrameRenderer.java revision 0b3562db3e01abce88f20bf2faeba61cce00d438
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
17package com.android.internal.policy;
18
19import com.android.internal.widget.NonClientDecorView;
20
21import android.graphics.Rect;
22import android.graphics.drawable.Drawable;
23import android.os.Looper;
24import android.view.Choreographer;
25import android.view.DisplayListCanvas;
26import android.view.RenderNode;
27import android.view.ThreadedRenderer;
28import android.view.View;
29
30/**
31 * The thread which draws a fill in background while the app is resizing in areas where the app
32 * content draw is lagging behind the resize operation.
33 * It starts with the creation and it ends once someone calls destroy().
34 * Any size changes can be passed by a call to setTargetRect will passed to the thread and
35 * executed via the Choreographer.
36 * @hide
37 */
38public class BackdropFrameRenderer extends Thread implements Choreographer.FrameCallback {
39
40    private NonClientDecorView mNonClientDecorView;
41
42    // This is containing the last requested size by a resize command. Note that this size might
43    // or might not have been applied to the output already.
44    private final Rect mTargetRect = new Rect();
45
46    // The render nodes for the multi threaded renderer.
47    private ThreadedRenderer mRenderer;
48    private RenderNode mFrameAndBackdropNode;
49
50    private final Rect mOldTargetRect = new Rect();
51    private final Rect mNewTargetRect = new Rect();
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    public BackdropFrameRenderer(NonClientDecorView nonClientDecorView,
66            ThreadedRenderer renderer,
67            Rect initialBounds) {
68        mNonClientDecorView = nonClientDecorView;
69        setName("ResizeFrame");
70        mRenderer = renderer;
71
72        // Create a render node for the content and frame backdrop
73        // which can be resized independently from the content.
74        mFrameAndBackdropNode = RenderNode.create("FrameAndBackdropNode", null);
75
76        mRenderer.addRenderNode(mFrameAndBackdropNode, true);
77
78        // Set the initial bounds and draw once so that we do not get a broken frame.
79        mTargetRect.set(initialBounds);
80        synchronized (this) {
81            changeWindowSizeLocked(initialBounds);
82        }
83
84        // Kick off our draw thread.
85        start();
86    }
87
88    /**
89     * Call this function asynchronously when the window size has been changed. The change will
90     * be picked up once per frame and the frame will be re-rendered accordingly.
91     * @param newTargetBounds The new target bounds.
92     */
93    public void setTargetRect(Rect newTargetBounds) {
94        synchronized (this) {
95            mTargetRect.set(newTargetBounds);
96            // Notify of a bounds change.
97            pingRenderLocked();
98        }
99    }
100
101    /**
102     * The window got replaced due to a configuration change.
103     */
104    public void onConfigurationChange() {
105        synchronized (this) {
106            if (mRenderer != null) {
107                // Enforce a window redraw.
108                mOldTargetRect.set(0, 0, 0, 0);
109                pingRenderLocked();
110            }
111        }
112    }
113
114    /**
115     * All resources of the renderer will be released. This function can be called from the
116     * the UI thread as well as the renderer thread.
117     */
118    public void releaseRenderer() {
119        synchronized (this) {
120            if (mRenderer != null) {
121                // Invalidate the current content bounds.
122                mRenderer.setContentDrawBounds(0, 0, 0, 0);
123
124                // Remove the render node again
125                // (see comment above - better to do that only once).
126                mRenderer.removeRenderNode(mFrameAndBackdropNode);
127
128                mRenderer = null;
129
130                // Exit the renderer loop.
131                pingRenderLocked();
132            }
133        }
134    }
135
136    @Override
137    public void run() {
138        try {
139            Looper.prepare();
140            synchronized (this) {
141                mChoreographer = Choreographer.getInstance();
142
143                // Draw at least once.
144                mChoreographer.postFrameCallback(this);
145            }
146            Looper.loop();
147        } finally {
148            releaseRenderer();
149        }
150        synchronized (this) {
151            // Make sure no more messages are being sent.
152            mChoreographer = null;
153        }
154    }
155
156    /**
157     * The implementation of the FrameCallback.
158     * @param frameTimeNanos The time in nanoseconds when the frame started being rendered,
159     * in the {@link System#nanoTime()} timebase.  Divide this value by {@code 1000000}
160     */
161    @Override
162    public void doFrame(long frameTimeNanos) {
163        synchronized (this) {
164            if (mRenderer == null) {
165                reportDrawIfNeeded();
166                // Tell the looper to stop. We are done.
167                Looper.myLooper().quit();
168                return;
169            }
170            mNewTargetRect.set(mTargetRect);
171            if (!mNewTargetRect.equals(mOldTargetRect) || mReportNextDraw) {
172                mOldTargetRect.set(mNewTargetRect);
173                changeWindowSizeLocked(mNewTargetRect);
174            }
175        }
176    }
177
178    /**
179     * The content is about to be drawn and we got the location of where it will be shown.
180     * If a "changeWindowSizeLocked" call has already been processed, we will re-issue the call
181     * if the previous call was ignored since the size was unknown.
182     * @param xOffset The x offset where the content is drawn to.
183     * @param yOffset The y offset where the content is drawn to.
184     * @param xSize The width size of the content. This should not be 0.
185     * @param ySize The height of the content.
186     * @return true if a frame should be requested after the content is drawn; false otherwise.
187     */
188    public boolean onContentDrawn(int xOffset, int yOffset, int xSize, int ySize) {
189        synchronized (this) {
190            final boolean firstCall = mLastContentWidth == 0;
191            // The current content buffer is drawn here.
192            mLastContentWidth = xSize;
193            mLastContentHeight = ySize - mLastCaptionHeight;
194            mLastXOffset = xOffset;
195            mLastYOffset = yOffset;
196
197            mRenderer.setContentDrawBounds(
198                    mLastXOffset,
199                    mLastYOffset,
200                    mLastXOffset + mLastContentWidth,
201                    mLastYOffset + mLastCaptionHeight + mLastContentHeight);
202            // If this was the first call and changeWindowSizeLocked got already called prior
203            // to us, we should re-issue a changeWindowSizeLocked now.
204            return firstCall && (mLastCaptionHeight != 0 || !mNonClientDecorView.mShowDecor);
205        }
206    }
207
208    public void onRequestDraw(boolean reportNextDraw) {
209        synchronized (this) {
210            mReportNextDraw = reportNextDraw;
211            mOldTargetRect.set(0, 0, 0, 0);
212            pingRenderLocked();
213        }
214    }
215
216    /**
217     * Resizing the frame to fit the new window size.
218     * @param newBounds The window bounds which needs to be drawn.
219     */
220    private void changeWindowSizeLocked(Rect newBounds) {
221        // While a configuration change is taking place the view hierarchy might become
222        // inaccessible. For that case we remember the previous metrics to avoid flashes.
223        // Note that even when there is no visible caption, the caption child will exist.
224        View caption = mNonClientDecorView.getChildAt(0);
225        if (caption != null) {
226            final int captionHeight = caption.getHeight();
227            // The caption height will probably never dynamically change while we are resizing.
228            // Once set to something other then 0 it should be kept that way.
229            if (captionHeight != 0) {
230                // Remember the height of the caption.
231                mLastCaptionHeight = captionHeight;
232            }
233        }
234        // Make sure that the other thread has already prepared the render draw calls for the
235        // content. If any size is 0, we have to wait for it to be drawn first.
236        if ((mLastCaptionHeight == 0 && mNonClientDecorView.mShowDecor) ||
237                mLastContentWidth == 0 || mLastContentHeight == 0) {
238            return;
239        }
240        // Since the surface is spanning the entire screen, we have to add the start offset of
241        // the bounds to get to the surface location.
242        final int left = mLastXOffset + newBounds.left;
243        final int top = mLastYOffset + newBounds.top;
244        final int width = newBounds.width();
245        final int height = newBounds.height();
246
247        mFrameAndBackdropNode.setLeftTopRightBottom(left, top, left + width, top + height);
248
249        // Draw the caption and content backdrops in to our render node.
250        DisplayListCanvas canvas = mFrameAndBackdropNode.start(width, height);
251        mNonClientDecorView.mCaptionBackgroundDrawable.setBounds(
252                0, 0, left + width, top + mLastCaptionHeight);
253        mNonClientDecorView.mCaptionBackgroundDrawable.draw(canvas);
254
255        // The backdrop: clear everything with the background. Clipping is done elsewhere.
256        mNonClientDecorView.mResizingBackgroundDrawable.setBounds(
257                0, mLastCaptionHeight, left + width, top + height);
258        mNonClientDecorView.mResizingBackgroundDrawable.draw(canvas);
259        mFrameAndBackdropNode.end(canvas);
260
261        // We need to render the node explicitly
262        mRenderer.drawRenderNode(mFrameAndBackdropNode);
263
264        reportDrawIfNeeded();
265    }
266
267    /**
268     * Notify view root that a frame has been drawn by us, if it has requested so.
269     */
270    private void reportDrawIfNeeded() {
271        if (mReportNextDraw) {
272            if (mNonClientDecorView.isAttachedToWindow()) {
273                mNonClientDecorView.getViewRootImpl().reportDrawFinish();
274            }
275            mReportNextDraw = false;
276        }
277    }
278
279    /**
280     * Sends a message to the renderer to wake up and perform the next action which can be
281     * either the next rendering or the self destruction if mRenderer is null.
282     * Note: This call must be synchronized.
283     */
284    private void pingRenderLocked() {
285        if (mChoreographer != null) {
286            mChoreographer.postFrameCallback(this);
287        }
288    }
289}
290