Merge "BackgroundFallback: Cover all cases where the fallback is needed" into pi-dev

am: 0d58b9bb64

Change-Id: I158c05e9bd15f9dda46bbcb09a313d7932d72c02
This commit is contained in:
android-build-team Robot
2018-05-03 07:06:35 -07:00
committed by android-build-merger
3 changed files with 339 additions and 11 deletions

View File

@@ -308,10 +308,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
public void onDraw(Canvas c) {
super.onDraw(c);
// When we are resizing, we need the fallback background to cover the area where we have our
// system bar background views as the navigation bar will be hidden during resizing.
mBackgroundFallback.draw(isResizing() ? this : mContentRoot, mContentRoot, c,
mWindow.mContentParent);
mBackgroundFallback.draw(this, mContentRoot, c, mWindow.mContentParent,
mStatusColorViewState.view, mNavigationColorViewState.view);
}
@Override

View File

@@ -46,8 +46,11 @@ public class BackgroundFallback {
* @param root The view group containing the content.
* @param c The canvas to draw the background onto.
* @param content The view where the actual app content is contained in.
* @param coveringView1 A potentially opaque view drawn atop the content
* @param coveringView2 A potentially opaque view drawn atop the content
*/
public void draw(ViewGroup boundsView, ViewGroup root, Canvas c, View content) {
public void draw(ViewGroup boundsView, ViewGroup root, Canvas c, View content,
View coveringView1, View coveringView2) {
if (!hasFallback()) {
return;
}
@@ -55,6 +58,10 @@ public class BackgroundFallback {
// Draw the fallback in the padding.
final int width = boundsView.getWidth();
final int height = boundsView.getHeight();
final int rootOffsetX = root.getLeft();
final int rootOffsetY = root.getTop();
int left = width;
int top = height;
int right = 0;
@@ -71,17 +78,58 @@ public class BackgroundFallback {
((ViewGroup) child).getChildCount() == 0) {
continue;
}
} else if (child.getVisibility() != View.VISIBLE || childBg == null ||
childBg.getOpacity() != PixelFormat.OPAQUE) {
} else if (child.getVisibility() != View.VISIBLE || !isOpaque(childBg)) {
// 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());
left = Math.min(left, rootOffsetX + child.getLeft());
top = Math.min(top, rootOffsetY + child.getTop());
right = Math.max(right, rootOffsetX + child.getRight());
bottom = Math.max(bottom, rootOffsetY + child.getBottom());
}
// If one of the bar backgrounds is a solid color and covers the entire padding on a side
// we can drop that padding.
boolean eachBarCoversTopInY = true;
for (int i = 0; i < 2; i++) {
View v = (i == 0) ? coveringView1 : coveringView2;
if (v == null || v.getVisibility() != View.VISIBLE
|| v.getAlpha() != 1f || !isOpaque(v.getBackground())) {
eachBarCoversTopInY = false;
continue;
}
// Bar covers entire left padding
if (v.getTop() <= 0 && v.getBottom() >= height
&& v.getLeft() <= 0 && v.getRight() >= left) {
left = 0;
}
// Bar covers entire right padding
if (v.getTop() <= 0 && v.getBottom() >= height
&& v.getLeft() <= right && v.getRight() >= width) {
right = width;
}
// Bar covers entire top padding
if (v.getTop() <= 0 && v.getBottom() >= top
&& v.getLeft() <= 0 && v.getRight() >= width) {
top = 0;
}
// Bar covers entire bottom padding
if (v.getTop() <= bottom && v.getBottom() >= height
&& v.getLeft() <= 0 && v.getRight() >= width) {
bottom = height;
}
eachBarCoversTopInY &= v.getTop() <= 0 && v.getBottom() >= top;
}
// Special case: Sometimes, both covering views together may cover the top inset, but
// neither does on its own.
if (eachBarCoversTopInY && (viewsCoverEntireWidth(coveringView1, coveringView2, width)
|| viewsCoverEntireWidth(coveringView2, coveringView1, width))) {
top = 0;
}
if (left >= right || top >= bottom) {
@@ -106,4 +154,24 @@ public class BackgroundFallback {
mBackgroundFallback.draw(c);
}
}
private boolean isOpaque(Drawable childBg) {
return childBg != null && childBg.getOpacity() == PixelFormat.OPAQUE;
}
/**
* Returns true if {@code view1} starts before or on {@code 0} and extends at least
* up to {@code view2}, and that view extends at least to {@code width}.
*
* @param view1 the first view to check if it covers the width
* @param view2 the second view to check if it covers the width
* @param width the width to check for
* @return returns true if both views together cover the entire width (and view1 is to the left
* of view2)
*/
private boolean viewsCoverEntireWidth(View view1, View view2, int width) {
return view1.getLeft() <= 0
&& view1.getRight() >= view2.getLeft()
&& view2.getRight() >= width;
}
}

View File

@@ -0,0 +1,262 @@
/*
* Copyright (C) 2018 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 static android.view.View.VISIBLE;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.support.test.InstrumentationRegistry;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import org.junit.Before;
import org.junit.Test;
import java.util.List;
public class BackgroundFallbackTest {
private static final int NAVBAR_BOTTOM = 0;
private static final int NAVBAR_LEFT = 1;
private static final int NAVBAR_RIGHT = 2;
private static final int SCREEN_HEIGHT = 2000;
private static final int SCREEN_WIDTH = 1000;
private static final int STATUS_HEIGHT = 100;
private static final int NAV_SIZE = 200;
private static final boolean INSET_CONTENT_VIEWS = true;
private static final boolean DONT_INSET_CONTENT_VIEWS = false;
BackgroundFallback mFallback;
Drawable mDrawableMock;
ViewGroup mStatusBarView;
ViewGroup mNavigationBarView;
ViewGroup mDecorViewMock;
ViewGroup mContentRootMock;
ViewGroup mContentContainerMock;
ViewGroup mContentMock;
int mLastTop = 0;
@Before
public void setUp() throws Exception {
mFallback = new BackgroundFallback();
mDrawableMock = mock(Drawable.class);
mFallback.setDrawable(mDrawableMock);
}
@Test
public void hasFallback_withDrawable_true() {
mFallback.setDrawable(mDrawableMock);
assertThat(mFallback.hasFallback(), is(true));
}
@Test
public void hasFallback_withoutDrawable_false() {
mFallback.setDrawable(null);
assertThat(mFallback.hasFallback(), is(false));
}
@Test
public void draw_portrait_noFallback() {
setUpViewHierarchy(INSET_CONTENT_VIEWS, NAVBAR_BOTTOM);
mFallback.draw(mDecorViewMock, mContentRootMock, null /* canvas */, mContentContainerMock,
mStatusBarView, mNavigationBarView);
verifyNoMoreInteractions(mDrawableMock);
}
@Test
public void draw_portrait_translucentBars_fallback() {
setUpViewHierarchy(INSET_CONTENT_VIEWS, NAVBAR_BOTTOM);
setTranslucent(mStatusBarView);
setTranslucent(mNavigationBarView);
mFallback.draw(mDecorViewMock, mContentRootMock, null /* canvas */, mContentContainerMock,
mStatusBarView, mNavigationBarView);
verifyFallbackTop(STATUS_HEIGHT);
verifyFallbackBottom(NAV_SIZE);
verifyNoMoreInteractions(mDrawableMock);
}
@Test
public void draw_landscape_translucentBars_fallback() {
setUpViewHierarchy(INSET_CONTENT_VIEWS, NAVBAR_RIGHT);
setTranslucent(mStatusBarView);
setTranslucent(mNavigationBarView);
mFallback.draw(mDecorViewMock, mContentRootMock, null /* canvas */, mContentContainerMock,
mStatusBarView, mNavigationBarView);
verifyFallbackTop(STATUS_HEIGHT);
verifyFallbackRight(NAV_SIZE);
verifyNoMoreInteractions(mDrawableMock);
}
@Test
public void draw_seascape_translucentBars_fallback() {
setUpViewHierarchy(INSET_CONTENT_VIEWS, NAVBAR_LEFT);
setTranslucent(mStatusBarView);
setTranslucent(mNavigationBarView);
mFallback.draw(mDecorViewMock, mContentRootMock, null /* canvas */, mContentContainerMock,
mStatusBarView, mNavigationBarView);
verifyFallbackTop(STATUS_HEIGHT);
verifyFallbackLeft(NAV_SIZE);
verifyNoMoreInteractions(mDrawableMock);
}
@Test
public void draw_landscape_noFallback() {
setUpViewHierarchy(INSET_CONTENT_VIEWS, NAVBAR_RIGHT);
mFallback.draw(mDecorViewMock, mContentRootMock, null /* canvas */, mContentContainerMock,
mStatusBarView, mNavigationBarView);
verifyNoMoreInteractions(mDrawableMock);
}
@Test
public void draw_seascape_noFallback() {
setUpViewHierarchy(INSET_CONTENT_VIEWS, NAVBAR_LEFT);
mFallback.draw(mDecorViewMock, mContentRootMock, null /* canvas */, mContentContainerMock,
mStatusBarView, mNavigationBarView);
verifyNoMoreInteractions(mDrawableMock);
}
@Test
public void draw_seascape_translucentBars_noInsets_noFallback() {
setUpViewHierarchy(DONT_INSET_CONTENT_VIEWS, NAVBAR_LEFT);
setTranslucent(mStatusBarView);
setTranslucent(mNavigationBarView);
mFallback.draw(mDecorViewMock, mContentRootMock, null /* canvas */, mContentContainerMock,
mStatusBarView, mNavigationBarView);
verifyNoMoreInteractions(mDrawableMock);
}
private void verifyFallbackTop(int size) {
verify(mDrawableMock).setBounds(0, 0, SCREEN_WIDTH, size);
verify(mDrawableMock, atLeastOnce()).draw(any());
mLastTop = size;
}
private void verifyFallbackLeft(int size) {
verify(mDrawableMock).setBounds(0, mLastTop, size, SCREEN_HEIGHT);
verify(mDrawableMock, atLeastOnce()).draw(any());
}
private void verifyFallbackRight(int size) {
verify(mDrawableMock).setBounds(SCREEN_WIDTH - size, mLastTop, SCREEN_WIDTH, SCREEN_HEIGHT);
verify(mDrawableMock, atLeastOnce()).draw(any());
}
private void verifyFallbackBottom(int size) {
verify(mDrawableMock).setBounds(0, SCREEN_HEIGHT - size, SCREEN_WIDTH, SCREEN_HEIGHT);
verify(mDrawableMock, atLeastOnce()).draw(any());
}
private void setUpViewHierarchy(boolean insetContentViews, int navBarPosition) {
int insetLeft = 0;
int insetTop = 0;
int insetRight = 0;
int insetBottom = 0;
mStatusBarView = mockView(0, 0, SCREEN_WIDTH, STATUS_HEIGHT,
new ColorDrawable(Color.BLACK), VISIBLE, emptyList());
if (insetContentViews) {
insetTop = STATUS_HEIGHT;
}
switch (navBarPosition) {
case NAVBAR_BOTTOM:
mNavigationBarView = mockView(0, SCREEN_HEIGHT - NAV_SIZE, SCREEN_WIDTH,
SCREEN_HEIGHT, new ColorDrawable(Color.BLACK), VISIBLE, emptyList());
if (insetContentViews) {
insetBottom = NAV_SIZE;
}
break;
case NAVBAR_LEFT:
mNavigationBarView = mockView(0, 0, NAV_SIZE, SCREEN_HEIGHT,
new ColorDrawable(Color.BLACK), VISIBLE, emptyList());
if (insetContentViews) {
insetLeft = NAV_SIZE;
}
break;
case NAVBAR_RIGHT:
mNavigationBarView = mockView(SCREEN_WIDTH - NAV_SIZE, 0, SCREEN_WIDTH,
SCREEN_HEIGHT, new ColorDrawable(Color.BLACK), VISIBLE, emptyList());
if (insetContentViews) {
insetRight = NAV_SIZE;
}
break;
}
mContentMock = mockView(0, 0, SCREEN_WIDTH - insetLeft - insetRight,
SCREEN_HEIGHT - insetTop - insetBottom, null, VISIBLE, emptyList());
mContentContainerMock = mockView(0, 0, SCREEN_WIDTH - insetLeft - insetRight,
SCREEN_HEIGHT - insetTop - insetBottom, null, VISIBLE, asList(mContentMock));
mContentRootMock = mockView(insetLeft, insetTop, SCREEN_WIDTH - insetRight,
SCREEN_HEIGHT - insetBottom, null, VISIBLE, asList(mContentContainerMock));
mDecorViewMock = mockView(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, null, VISIBLE,
asList(mContentRootMock, mStatusBarView, mNavigationBarView));
}
private void setTranslucent(ViewGroup bar) {
bar.setBackground(new ColorDrawable(Color.TRANSPARENT));
}
private ViewGroup mockView(int left, int top, int right, int bottom, Drawable background,
int visibility, List<ViewGroup> children) {
final ViewGroup v = new FrameLayout(InstrumentationRegistry.getTargetContext());
v.layout(left, top, right, bottom);
v.setBackground(background);
v.setVisibility(visibility);
for (ViewGroup c : children) {
v.addView(c);
}
return v;
}
}