[go: nahoru, domu]

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 android.graphics.Rect;
20import android.graphics.drawable.ColorDrawable;
21import android.graphics.drawable.Drawable;
22import android.os.Looper;
23import android.view.Choreographer;
24import android.view.DisplayListCanvas;
25import android.view.RenderNode;
26import 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 */
36public 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
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
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
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     */
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     */
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     */
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
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
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
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     */
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
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     */
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
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        if (mStatusBarColor != null) {
375            mStatusBarColor.setBounds(0, 0, left + width, topInset);
376            mStatusBarColor.draw(canvas);
377        }
378
379        // We only want to draw the navigation bar if our window is currently fullscreen because we
380        // don't want the navigation bar background be moving around when resizing in docked mode.
381        // However, we need it for the transitions into/out of docked mode.
382        if (mNavigationBarColor != null && fullscreen) {
383            final int size = DecorView.getNavBarSize(bottomInset, rightInset);
384            if (DecorView.isNavBarToRightEdge(bottomInset, rightInset)) {
385                mNavigationBarColor.setBounds(width - size, 0, width, height);
386            } else {
387                mNavigationBarColor.setBounds(0, height - size, width, height);
388            }
389            mNavigationBarColor.draw(canvas);
390        }
391        mSystemBarBackgroundNode.end(canvas);
392        mRenderer.drawRenderNode(mSystemBarBackgroundNode);
393    }
394
395    /** Notify view root that a frame has been drawn by us, if it has requested so. */
396    private void reportDrawIfNeeded() {
397        if (mReportNextDraw) {
398            if (mDecorView.isAttachedToWindow()) {
399                mDecorView.getViewRootImpl().reportDrawFinish();
400            }
401            mReportNextDraw = false;
402        }
403    }
404
405    /**
406     * Sends a message to the renderer to wake up and perform the next action which can be
407     * either the next rendering or the self destruction if mRenderer is null.
408     * Note: This call must be synchronized.
409     *
410     * @param drawImmediate if we should draw immediately instead of scheduling a frame
411     */
412    private void pingRenderLocked(boolean drawImmediate) {
413        if (mChoreographer != null && !drawImmediate) {
414            mChoreographer.postFrameCallback(this);
415        } else {
416            doFrameUncheckedLocked();
417        }
418    }
419
420    void setUserCaptionBackgroundDrawable(Drawable userCaptionBackgroundDrawable) {
421        mUserCaptionBackgroundDrawable = userCaptionBackgroundDrawable;
422    }
423}
424