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++; + } } }