From f849a5e16d016fb6ae081a4575ce67f4ce688e3a Mon Sep 17 00:00:00 2001 From: Adam Powell Date: Thu, 11 Sep 2014 15:09:36 -0700 Subject: [PATCH] Magic null-background filling for PhoneWindows In the past it's been a recommended approach to avoiding overdraw for apps to set their window background to null at runtime if their content view fully covers their window surface. The problem with this is the IME. The IME can force a resize of the window at unexpected times and unless an app has been configured to fit system windows and manually cover the padded area that the IME window covers, the asynchronous nature of the IME-show process can leave surface buffer garbage visible to the user. In previous platform versions this wasn't an issue since pre-renderthread we would always animate a crossfade from the closed to open state. This animation was always a bit of a hack since it could break the contract of requestLayout/invalidate on the view hierarchy - it could result in a draw happening into the saved "before" state of the crossfade before a pending layout. Now that this has been cleaned up the buffer garbage is sometimes visible. To prevent this, PhoneWindow now detects the state of a null window background and draws solid rects into the area not covered by a window's content. Which color is determined by the window context's theme, though this is not a public API available to apps. Bug 17006497 Change-Id: I714439a1608c4ae135f3d9d49bb165330d9fbe9f --- .../internal/widget/ActionBarContainer.java | 19 +++- .../internal/widget/BackgroundFallback.java | 101 ++++++++++++++++++ .../internal/widget/DecorContentParent.java | 1 - core/res/res/values/attrs.xml | 5 + core/res/res/values/symbols.xml | 1 + core/res/res/values/themes.xml | 1 + core/res/res/values/themes_material.xml | 4 +- .../internal/policy/impl/PhoneWindow.java | 23 ++++ 8 files changed, 151 insertions(+), 4 deletions(-) create mode 100644 core/java/com/android/internal/widget/BackgroundFallback.java diff --git a/core/java/com/android/internal/widget/ActionBarContainer.java b/core/java/com/android/internal/widget/ActionBarContainer.java index 8111e637a9173..d134dd4ceded1 100644 --- a/core/java/com/android/internal/widget/ActionBarContainer.java +++ b/core/java/com/android/internal/widget/ActionBarContainer.java @@ -22,6 +22,7 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Outline; +import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.ActionMode; @@ -374,7 +375,23 @@ public class ActionBarContainer extends FrameLayout { @Override public int getOpacity() { - return 0; + if (mIsSplit) { + if (mSplitBackground != null + && mSplitBackground.getOpacity() == PixelFormat.OPAQUE) { + return PixelFormat.OPAQUE; + } + } else { + if (mIsStacked && (mStackedBackground == null + || mStackedBackground.getOpacity() != PixelFormat.OPAQUE)) { + return PixelFormat.UNKNOWN; + } + if (!isCollapsed(mActionBarView) && mBackground != null + && mBackground.getOpacity() == PixelFormat.OPAQUE) { + return PixelFormat.OPAQUE; + } + } + + return PixelFormat.UNKNOWN; } } } diff --git a/core/java/com/android/internal/widget/BackgroundFallback.java b/core/java/com/android/internal/widget/BackgroundFallback.java new file mode 100644 index 0000000000000..4adba4db4f2e4 --- /dev/null +++ b/core/java/com/android/internal/widget/BackgroundFallback.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.android.internal.widget; + +import android.graphics.Canvas; +import android.graphics.PixelFormat; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.view.ViewGroup; + +/** + * Helper class for drawing a fallback background in framework decor layouts. + * Useful for when an app has not set a window background but we're asked to draw + * an uncovered area. + */ +public class BackgroundFallback { + private Drawable mBackgroundFallback; + + public void setDrawable(Drawable d) { + mBackgroundFallback = d; + } + + public boolean hasFallback() { + return mBackgroundFallback != null; + } + + public void draw(ViewGroup root, Canvas c, View content) { + if (!hasFallback()) { + return; + } + + // Draw the fallback in the padding. + final int width = root.getWidth(); + final int height = root.getHeight(); + int left = width; + int top = height; + int right = 0; + int bottom = 0; + + final int childCount = root.getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = root.getChildAt(i); + final Drawable childBg = child.getBackground(); + if (child == content) { + // We always count the content view container unless it has no background + // and no children. + if (childBg == null && child instanceof ViewGroup && + ((ViewGroup) child).getChildCount() == 0) { + continue; + } + } else if (child.getVisibility() != View.VISIBLE || childBg == null || + childBg.getOpacity() != PixelFormat.OPAQUE) { + // Potentially translucent or invisible children don't count, and we assume + // the content view will cover the whole area if we're in a background + // fallback situation. + continue; + } + left = Math.min(left, child.getLeft()); + top = Math.min(top, child.getTop()); + right = Math.max(right, child.getRight()); + bottom = Math.max(bottom, child.getBottom()); + } + + if (left >= right || top >= bottom) { + // No valid area to draw in. + return; + } + + if (top > 0) { + mBackgroundFallback.setBounds(0, 0, width, top); + mBackgroundFallback.draw(c); + } + if (left > 0) { + mBackgroundFallback.setBounds(0, top, left, height); + mBackgroundFallback.draw(c); + } + if (right < width) { + mBackgroundFallback.setBounds(right, top, width, height); + mBackgroundFallback.draw(c); + } + if (bottom < height) { + mBackgroundFallback.setBounds(left, bottom, right, height); + mBackgroundFallback.draw(c); + } + } +} diff --git a/core/java/com/android/internal/widget/DecorContentParent.java b/core/java/com/android/internal/widget/DecorContentParent.java index 4fa370ad2bbaa..ac524f929d1ba 100644 --- a/core/java/com/android/internal/widget/DecorContentParent.java +++ b/core/java/com/android/internal/widget/DecorContentParent.java @@ -49,5 +49,4 @@ public interface DecorContentParent { void saveToolbarHierarchyState(SparseArray toolbarStates); void restoreToolbarHierarchyState(SparseArray toolbarStates); void dismissPopups(); - } diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 0e597d0c1a70b..516b08846b2c9 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -308,6 +308,10 @@ null so it will not be drawn. --> + + @@ -1772,6 +1776,7 @@ + diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index e4ca36d6718d2..2b2abb5d841c3 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2042,4 +2042,5 @@ + diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index d0097aacf7f3d..05776592d5124 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -169,6 +169,7 @@ please see themes_device_defaults.xml. @drawable/screen_background_selector_dark + ?attr/colorBackground false @null false diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml index 008e1700d215c..aefca726eacb7 100644 --- a/core/res/res/values/themes_material.xml +++ b/core/res/res/values/themes_material.xml @@ -145,7 +145,7 @@ please see themes_device_defaults.xml. @drawable/gallery_item_background - @color/background_material_dark + ?attr/colorBackground true @null false @@ -489,7 +489,7 @@ please see themes_device_defaults.xml. @drawable/gallery_item_background - @color/background_material_light + ?attr/colorBackground true @null false diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index 92171c1c4d129..6f7f1fb595538 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -33,6 +33,7 @@ import com.android.internal.view.menu.MenuDialogHelper; import com.android.internal.view.menu.MenuPresenter; import com.android.internal.view.menu.MenuView; import com.android.internal.widget.ActionBarContextView; +import com.android.internal.widget.BackgroundFallback; import com.android.internal.widget.DecorContentParent; import com.android.internal.widget.SwipeDismissLayout; @@ -211,6 +212,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private ProgressBar mHorizontalProgressBar; private int mBackgroundResource = 0; + private int mBackgroundFallbackResource = 0; private Drawable mBackgroundDrawable; @@ -1326,6 +1328,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (mDecor != null) { mDecor.setWindowBackground(drawable); } + if (mBackgroundFallbackResource != 0) { + mDecor.setBackgroundFallback(drawable != null ? 0 : mBackgroundFallbackResource); + } } } @@ -2153,6 +2158,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private View mStatusColorView; private View mNavigationColorView; + private final BackgroundFallback mBackgroundFallback = new BackgroundFallback(); private int mLastTopInset = 0; private int mLastBottomInset = 0; @@ -2165,6 +2171,17 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { mFeatureId = featureId; } + public void setBackgroundFallback(int resId) { + mBackgroundFallback.setDrawable(resId != 0 ? getContext().getDrawable(resId) : null); + setWillNotDraw(getBackground() == null && !mBackgroundFallback.hasFallback()); + } + + @Override + public void onDraw(Canvas c) { + super.onDraw(c); + mBackgroundFallback.draw(mContentRoot, c, mContentParent); + } + @Override public boolean dispatchKeyEvent(KeyEvent event) { final int keyCode = event.getKeyCode(); @@ -3342,6 +3359,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (mFrameResource == 0) { mFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0); } + mBackgroundFallbackResource = a.getResourceId( + R.styleable.Window_windowBackgroundFallback, 0); if (false) { System.out.println("Background: " + Integer.toHexString(mBackgroundResource) + " Frame: " @@ -3557,6 +3576,10 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } } + if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) { + mDecor.setBackgroundFallback(mBackgroundFallbackResource); + } + // Only inflate or create a new TransitionManager if the caller hasn't // already set a custom one. if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {