Merge "BackgroundFallback: Cover all cases where the fallback is needed" into pi-dev
am: 0d58b9bb64
Change-Id: I158c05e9bd15f9dda46bbcb09a313d7932d72c02
This commit is contained in:
committed by
android-build-merger
commit
63180005bb
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user