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.RecordingCanvas; 20 import android.graphics.Rect; 21 import android.graphics.RenderNode; 22 import android.graphics.drawable.ColorDrawable; 23 import android.graphics.drawable.Drawable; 24 import android.os.Looper; 25 import android.view.Choreographer; 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 Rect mOldSystemInsets = new Rect(); 73 private final Rect mOldStableInsets = new Rect(); 74 private final Rect mSystemInsets = new Rect(); 75 private final Rect mStableInsets = new Rect(); 76 private final Rect mTmpRect = 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)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) { 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 103 // Kick off our draw thread. 104 start(); 105 } 106 onResourcesLoaded(DecorView decorView, Drawable resizingBackgroundDrawable, Drawable captionBackgroundDrawableDrawable, Drawable userCaptionBackgroundDrawable, int statusBarColor, int navigationBarColor)107 void onResourcesLoaded(DecorView decorView, Drawable resizingBackgroundDrawable, 108 Drawable captionBackgroundDrawableDrawable, Drawable userCaptionBackgroundDrawable, 109 int statusBarColor, int navigationBarColor) { 110 synchronized (this) { 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 addSystemBarNodeIfNeeded()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 */ setTargetRect(Rect newTargetBounds, boolean fullscreen, Rect systemInsets, Rect stableInsets)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 */ onConfigurationChange()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 */ releaseRenderer()189 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 run()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 doFrame(long frameTimeNanos)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 doFrameUncheckedLocked()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 */ onContentDrawn(int xOffset, int yOffset, int xSize, int ySize)271 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 onRequestDraw(boolean reportNextDraw)294 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 */ redrawLocked(Rect newBounds, boolean fullscreen, Rect systemInsets, Rect stableInsets)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 // Content may not be drawn at the surface origin, so we want to keep the offset when we're 333 // resizing it. 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 RecordingCanvas canvas = mFrameAndBackdropNode.beginRecording(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.endRecording(); 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 drawColorViews(int left, int top, int width, int height, boolean fullscreen, Rect systemInsets, Rect stableInsets)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 RecordingCanvas canvas = mSystemBarBackgroundNode.beginRecording(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, 1f); 384 mNavigationBarColor.setBounds(mTmpRect); 385 mNavigationBarColor.draw(canvas); 386 } 387 mSystemBarBackgroundNode.endRecording(); 388 mRenderer.drawRenderNode(mSystemBarBackgroundNode); 389 } 390 391 /** Notify view root that a frame has been drawn by us, if it has requested so. */ reportDrawIfNeeded()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 */ pingRenderLocked(boolean drawImmediate)408 private void pingRenderLocked(boolean drawImmediate) { 409 if (mChoreographer != null && !drawImmediate) { 410 mChoreographer.postFrameCallback(this); 411 } else { 412 doFrameUncheckedLocked(); 413 } 414 } 415 setUserCaptionBackgroundDrawable(Drawable userCaptionBackgroundDrawable)416 void setUserCaptionBackgroundDrawable(Drawable userCaptionBackgroundDrawable) { 417 synchronized (this) { 418 mUserCaptionBackgroundDrawable = userCaptionBackgroundDrawable; 419 } 420 } 421 } 422