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
diff --git a/core/java/com/android/internal/widget/ActionBarContainer.java b/core/java/com/android/internal/widget/ActionBarContainer.java
index 8111e63..d134dd4 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.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 @@
@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 0000000..4adba4d
--- /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 4fa370a..ac524f9 100644
--- a/core/java/com/android/internal/widget/DecorContentParent.java
+++ b/core/java/com/android/internal/widget/DecorContentParent.java
@@ -49,5 +49,4 @@
void saveToolbarHierarchyState(SparseArray<Parcelable> toolbarStates);
void restoreToolbarHierarchyState(SparseArray<Parcelable> toolbarStates);
void dismissPopups();
-
}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 0e597d0..516b088 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.
</ul> -->
<attr name="windowBackground" format="reference" />
+ <!-- Drawable to draw selectively within the inset areas when the windowBackground
+ has been set to null. This protects against seeing visual garbage in the
+ surface when the app has not drawn any content into this area. -->
+ <attr name="windowBackgroundFallback" format="reference" />
<!-- Drawable to use as a frame around the window. -->
<attr name="windowFrame" format="reference" />
<!-- Flag indicating whether there should be no title on this window. -->
@@ -1772,6 +1776,7 @@
<!-- The set of attributes that describe a Windows's theme. -->
<declare-styleable name="Window">
<attr name="windowBackground" />
+ <attr name="windowBackgroundFallback" />
<attr name="windowContentOverlay" />
<attr name="windowFrame" />
<attr name="windowNoTitle" />
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e4ca36d..2b2abb5 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2042,4 +2042,5 @@
<java-symbol type="layout" name="resolver_different_item_header" />
<java-symbol type="array" name="config_default_vm_number" />
<java-symbol type="integer" name="config_cdma_3waycall_flash_delay"/>
+ <java-symbol type="attr" name="windowBackgroundFallback" />
</resources>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index d0097aac..0577659 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -169,6 +169,7 @@
<!-- Window attributes -->
<item name="windowBackground">@drawable/screen_background_selector_dark</item>
+ <item name="windowBackgroundFallback">?attr/colorBackground</item>
<item name="windowClipToOutline">false</item>
<item name="windowFrame">@null</item>
<item name="windowNoTitle">false</item>
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index 008e170..aefca72 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -145,7 +145,7 @@
<item name="galleryItemBackground">@drawable/gallery_item_background</item>
<!-- Window attributes -->
- <item name="windowBackground">@color/background_material_dark</item>
+ <item name="windowBackground">?attr/colorBackground</item>
<item name="windowClipToOutline">true</item>
<item name="windowFrame">@null</item>
<item name="windowNoTitle">false</item>
@@ -489,7 +489,7 @@
<item name="galleryItemBackground">@drawable/gallery_item_background</item>
<!-- Window attributes -->
- <item name="windowBackground">@color/background_material_light</item>
+ <item name="windowBackground">?attr/colorBackground</item>
<item name="windowClipToOutline">true</item>
<item name="windowFrame">@null</item>
<item name="windowNoTitle">false</item>
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index 92171c1..6f7f1fb 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.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 @@
private ProgressBar mHorizontalProgressBar;
private int mBackgroundResource = 0;
+ private int mBackgroundFallbackResource = 0;
private Drawable mBackgroundDrawable;
@@ -1326,6 +1328,9 @@
if (mDecor != null) {
mDecor.setWindowBackground(drawable);
}
+ if (mBackgroundFallbackResource != 0) {
+ mDecor.setBackgroundFallback(drawable != null ? 0 : mBackgroundFallbackResource);
+ }
}
}
@@ -2153,6 +2158,7 @@
private View mStatusColorView;
private View mNavigationColorView;
+ private final BackgroundFallback mBackgroundFallback = new BackgroundFallback();
private int mLastTopInset = 0;
private int mLastBottomInset = 0;
@@ -2165,6 +2171,17 @@
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 @@
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 @@
}
}
+ 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)) {