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