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