Loading core/java/com/android/internal/policy/DecorView.java +2 −4 Original line number Diff line number Diff line Loading @@ -315,10 +315,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 Loading core/java/com/android/internal/widget/BackgroundFallback.java +75 −7 Original line number Diff line number Diff line Loading @@ -51,8 +51,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; } Loading @@ -60,6 +63,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; Loading @@ -76,17 +83,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) { Loading @@ -111,4 +159,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; } } core/tests/coretests/src/com/android/internal/widget/BackgroundFallbackTest.java 0 → 100644 +262 −0 Original line number Diff line number Diff line /* * 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; } } No newline at end of file Loading
core/java/com/android/internal/policy/DecorView.java +2 −4 Original line number Diff line number Diff line Loading @@ -315,10 +315,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 Loading
core/java/com/android/internal/widget/BackgroundFallback.java +75 −7 Original line number Diff line number Diff line Loading @@ -51,8 +51,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; } Loading @@ -60,6 +63,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; Loading @@ -76,17 +83,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) { Loading @@ -111,4 +159,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; } }
core/tests/coretests/src/com/android/internal/widget/BackgroundFallbackTest.java 0 → 100644 +262 −0 Original line number Diff line number Diff line /* * 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; } } No newline at end of file