summaryrefslogtreecommitdiff
path: root/core/java/com/android/internal/widget/BackgroundFallback.java
blob: a66fa65af257c67fc2677e747dc74933402ec7f2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
/*
 * 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.annotation.Nullable;
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 @Nullable Drawable getDrawable() {
        return mBackgroundFallback;
    }

    public boolean hasFallback() {
        return mBackgroundFallback != null;
    }

    /**
     * Draws the fallback background.
     *
     * @param boundsView The view determining with which bounds the background should be drawn.
     * @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,
            View coveringView1, View coveringView2) {
        if (!hasFallback()) {
            return;
        }

        // 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;
        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 || !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, 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) {
            // 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);
        }
    }

    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;
    }
}