Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9a63fa95 authored by android-build-team Robot's avatar android-build-team Robot Committed by android-build-merger
Browse files

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

am: 63180005

Change-Id: Ib584f14ae26d3b0ded27d80cab9a408699017e6a
parents 2e6d732a 63180005
Loading
Loading
Loading
Loading
+2 −4
Original line number Diff line number Diff line
@@ -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
+75 −7
Original line number Diff line number Diff line
@@ -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;
        }
@@ -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;
@@ -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) {
@@ -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;
    }
}
+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