From b3785307143556f3aba228f4c81fac96fda09ea1 Mon Sep 17 00:00:00 2001 From: Dianne Hackborn <> Date: Tue, 31 Mar 2009 17:58:46 -0700 Subject: [PATCH] AI 143896: Fix issue #1748954 and #1737952: #1748954 (New status bar fades into all white background): FrameLayout wasn't updating its foreground drawable when its padding changed, which would happen as the status bar is shown and hidden. To fix this I also ended up fixing a problem in the view debug stuff where we couldn't get a bitmap for a view that is the full screen size because it is too big... actually I just went ahead and added another function to snapshot the view hierarchy which works a lot better for us anyway. #1737952 (Home screen icons overlap with the notification bar after exiting any camera app): Originally I punted this because it only happened in rare situations, but now that home is always portrait it happens a lot more so it is more important to fix. This involved a few things to clean up hiding/showing the status bar: - We now determine when to hide and show it during layout, which allows us to do this at the time it is actually needed rather than during animation after we can actually catch it for the initial display of a window. This required tweaking the layout API so the policy can request a second layout pass if needed. - When doing layout, we are now much more aggressive about skipping the layout of windows. Basically anything that we know will be hidden in the near future is ignored for layout, so that it doesn't glitch as it is transfered out of the screen. The theory being that it is better to leave it as it was originally placed while we are transitioning it out, than to switch it to something slightly more correct. BUG=1748954,1737952 Automated import of CL 143896 --- core/java/android/view/View.java | 58 ++++++++++++++++++- core/java/android/view/ViewDebug.java | 33 ++++------- .../android/view/WindowManagerPolicy.java | 5 +- core/java/android/widget/FrameLayout.java | 54 +++++++++++------ .../android/server/WindowManagerService.java | 27 +++++++-- 5 files changed, 131 insertions(+), 46 deletions(-) diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 04447ca4719df..4a9356acab9af 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -5531,7 +5531,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback { (mBGDrawable != null && mBGDrawable.getOpacity() == PixelFormat.OPAQUE); if (width <= 0 || height <= 0 || - (width * height * (opaque ? 2 : 4) >= // Projected bitmap size in bytes + (width * height * (opaque ? 2 : 4) > // Projected bitmap size in bytes ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize())) { destroyDrawingCache(); return; @@ -5627,6 +5627,62 @@ public class View implements Drawable.Callback, KeyEvent.Callback { } } + /** + * Create a snapshot of the view into a bitmap. We should probably make + * some form of this public, but should think about the API. + */ + /*package*/ Bitmap createSnapshot(Bitmap.Config quality, int backgroundColor) { + final int width = mRight - mLeft; + final int height = mBottom - mTop; + + Bitmap bitmap = Bitmap.createBitmap(width, height, quality); + if (bitmap == null) { + throw new OutOfMemoryError(); + } + + Canvas canvas; + final AttachInfo attachInfo = mAttachInfo; + if (attachInfo != null) { + canvas = attachInfo.mCanvas; + if (canvas == null) { + canvas = new Canvas(); + } + canvas.setBitmap(bitmap); + // Temporarily clobber the cached Canvas in case one of our children + // is also using a drawing cache. Without this, the children would + // steal the canvas by attaching their own bitmap to it and bad, bad + // things would happen (invisible views, corrupted drawings, etc.) + attachInfo.mCanvas = null; + } else { + // This case should hopefully never or seldom happen + canvas = new Canvas(bitmap); + } + + if ((backgroundColor&0xff000000) != 0) { + bitmap.eraseColor(backgroundColor); + } + + computeScroll(); + final int restoreCount = canvas.save(); + canvas.translate(-mScrollX, -mScrollY); + + // Fast path for layouts with no backgrounds + if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) { + dispatchDraw(canvas); + } else { + draw(canvas); + } + + canvas.restoreToCount(restoreCount); + + if (attachInfo != null) { + // Restore the cached Canvas for our siblings + attachInfo.mCanvas = canvas; + } + + return bitmap; + } + /** * Indicates whether this View is currently in edit mode. A View is usually * in edit mode when displayed within a developer tool. For instance, if diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index f604bc56a06e7..c1e9ed8efa0b2 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -741,22 +741,18 @@ public class ViewDebug { final CountDownLatch latch = new CountDownLatch(1); final Bitmap[] cache = new Bitmap[1]; - final boolean hasCache = captureView.isDrawingCacheEnabled(); - final boolean willNotCache = captureView.willNotCacheDrawing(); - - if (willNotCache) { - // TODO: Should happen on the UI thread - captureView.setWillNotCacheDrawing(false); - } - root.post(new Runnable() { public void run() { try { - if (!hasCache) { - captureView.buildDrawingCache(); + cache[0] = captureView.createSnapshot( + Bitmap.Config.ARGB_8888, 0); + } catch (OutOfMemoryError e) { + try { + cache[0] = captureView.createSnapshot( + Bitmap.Config.ARGB_4444, 0); + } catch (OutOfMemoryError e2) { + Log.w("View", "Out of memory for bitmap"); } - - cache[0] = captureView.getDrawingCache(); } finally { latch.countDown(); } @@ -776,20 +772,15 @@ public class ViewDebug { if (out != null) { out.close(); } + cache[0].recycle(); } + } else { + Log.w("View", "Failed to create capture bitmap!"); + clientStream.close(); } } catch (InterruptedException e) { Log.w("View", "Could not complete the capture of the view " + captureView); Thread.currentThread().interrupt(); - } finally { - if (willNotCache) { - // TODO: Should happen on the UI thread - captureView.setWillNotCacheDrawing(true); - } - if (!hasCache) { - // TODO: Should happen on the UI thread - captureView.destroyDrawingCache(); - } } } } diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 220869c1d236e..13719323cb68b 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -580,8 +580,11 @@ public interface WindowManagerPolicy { * Called when layout of the windows is finished. After this function has * returned, all windows given to layoutWindow() must have had a * frame assigned. + * + * @return Return true if layout state may have changed (so that another + * layout will be performed). */ - public void finishLayoutLw(); + public boolean finishLayoutLw(); /** * Called when animation of the windows is about to start. diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java index 47db6f2745382..80fbf9eddecfd 100644 --- a/core/java/android/widget/FrameLayout.java +++ b/core/java/android/widget/FrameLayout.java @@ -24,6 +24,7 @@ import android.graphics.Region; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.View; +import android.view.ViewDebug; import android.view.ViewGroup; import android.view.Gravity; import android.widget.RemoteViews.RemoteView; @@ -45,21 +46,31 @@ import android.widget.RemoteViews.RemoteView; */ @RemoteView public class FrameLayout extends ViewGroup { + @ViewDebug.ExportedProperty boolean mMeasureAllChildren = false; + @ViewDebug.ExportedProperty private Drawable mForeground; + @ViewDebug.ExportedProperty private int mForegroundPaddingLeft = 0; + @ViewDebug.ExportedProperty private int mForegroundPaddingTop = 0; + @ViewDebug.ExportedProperty private int mForegroundPaddingRight = 0; + @ViewDebug.ExportedProperty private int mForegroundPaddingBottom = 0; private final Rect mSelfBounds = new Rect(); private final Rect mOverlayBounds = new Rect(); + @ViewDebug.ExportedProperty private int mForegroundGravity = Gravity.FILL; /** {@hide} */ + @ViewDebug.ExportedProperty protected boolean mForegroundInPadding = true; + boolean mForegroundBoundsChanged = false; + public FrameLayout(Context context) { super(context); } @@ -269,6 +280,8 @@ public class FrameLayout extends ViewGroup { final int parentTop = mPaddingTop + mForegroundPaddingTop; final int parentBottom = bottom - top - mPaddingBottom - mForegroundPaddingBottom; + mForegroundBoundsChanged = true; + for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { @@ -328,22 +341,7 @@ public class FrameLayout extends ViewGroup { @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); - - final Drawable foreground = mForeground; - if (foreground != null) { - final Rect selfBounds = mSelfBounds; - final Rect overlayBounds = mOverlayBounds; - - if (mForegroundInPadding) { - selfBounds.set(0, 0, w, h); - } else { - selfBounds.set(mPaddingLeft, mPaddingTop, w - mPaddingRight, h - mPaddingBottom); - } - - Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(), - foreground.getIntrinsicHeight(), selfBounds, overlayBounds); - foreground.setBounds(overlayBounds); - } + mForegroundBoundsChanged = true; } /** @@ -354,7 +352,29 @@ public class FrameLayout extends ViewGroup { super.draw(canvas); if (mForeground != null) { - mForeground.draw(canvas); + final Drawable foreground = mForeground; + if (mForegroundBoundsChanged) { + mForegroundBoundsChanged = false; + if (foreground != null) { + final Rect selfBounds = mSelfBounds; + final Rect overlayBounds = mOverlayBounds; + + final int w = mRight-mLeft; + final int h = mBottom-mTop; + + if (mForegroundInPadding) { + selfBounds.set(0, 0, w, h); + } else { + selfBounds.set(mPaddingLeft, mPaddingTop, w - mPaddingRight, h - mPaddingBottom); + } + + Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(), + foreground.getIntrinsicHeight(), selfBounds, overlayBounds); + foreground.setBounds(overlayBounds); + } + } + + foreground.draw(canvas); } } diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java index 270d15f6e7021..0b1ddc846be5d 100644 --- a/services/java/com/android/server/WindowManagerService.java +++ b/services/java/com/android/server/WindowManagerService.java @@ -7485,11 +7485,12 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo final int dh = mDisplay.getHeight(); final int N = mWindows.size(); + int repeats = 0; int i; // FIRST LOOP: Perform a layout, if needed. - if (mLayoutNeeded) { + while (mLayoutNeeded) { mPolicy.beginLayoutLw(dw, dh); // First perform layout of any root windows (not attached @@ -7497,10 +7498,18 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo int topAttached = -1; for (i = N-1; i >= 0; i--) { WindowState win = (WindowState) mWindows.get(i); - - boolean gone = win.mViewVisibility == View.GONE + + // Don't do layout of a window if it is not visible, or + // soon won't be visible, to avoid wasting time and funky + // changes while a window is animating away. + final AppWindowToken atoken = win.mAppToken; + final boolean gone = win.mViewVisibility == View.GONE || !win.mRelayoutCalled - || win.mRootToken.hidden; + || win.mRootToken.hidden + || (atoken != null && atoken.hiddenRequested) + || !win.mPolicyVisibility + || win.mAttachedHidden + || win.mExiting || win.mDestroying; // If this view is GONE, then skip it -- keep the current // frame, and let the caller know so they can ignore it @@ -7536,8 +7545,14 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdo } } - mPolicy.finishLayoutLw(); - mLayoutNeeded = false; + if (!mPolicy.finishLayoutLw()) { + mLayoutNeeded = false; + } else if (repeats > 2) { + Log.w(TAG, "Layout repeat aborted after too many iterations"); + mLayoutNeeded = false; + } else { + repeats++; + } } }