1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17
18package com.android.internal.widget;
19
20import android.graphics.Canvas;
21import android.graphics.PixelFormat;
22import android.graphics.drawable.Drawable;
23import android.view.View;
24import android.view.ViewGroup;
25
26/**
27 * Helper class for drawing a fallback background in framework decor layouts.
28 * Useful for when an app has not set a window background but we're asked to draw
29 * an uncovered area.
30 */
31public class BackgroundFallback {
32    private Drawable mBackgroundFallback;
33
34    public void setDrawable(Drawable d) {
35        mBackgroundFallback = d;
36    }
37
38    public boolean hasFallback() {
39        return mBackgroundFallback != null;
40    }
41
42    /**
43     * Draws the fallback background.
44     *
45     * @param boundsView The view determining with which bounds the background should be drawn.
46     * @param root The view group containing the content.
47     * @param c The canvas to draw the background onto.
48     * @param content The view where the actual app content is contained in.
49     * @param coveringView1 A potentially opaque view drawn atop the content
50     * @param coveringView2 A potentially opaque view drawn atop the content
51     */
52    public void draw(ViewGroup boundsView, ViewGroup root, Canvas c, View content,
53            View coveringView1, View coveringView2) {
54        if (!hasFallback()) {
55            return;
56        }
57
58        // Draw the fallback in the padding.
59        final int width = boundsView.getWidth();
60        final int height = boundsView.getHeight();
61
62        final int rootOffsetX = root.getLeft();
63        final int rootOffsetY = root.getTop();
64
65        int left = width;
66        int top = height;
67        int right = 0;
68        int bottom = 0;
69
70        final int childCount = root.getChildCount();
71        for (int i = 0; i < childCount; i++) {
72            final View child = root.getChildAt(i);
73            final Drawable childBg = child.getBackground();
74            if (child == content) {
75                // We always count the content view container unless it has no background
76                // and no children.
77                if (childBg == null && child instanceof ViewGroup &&
78                        ((ViewGroup) child).getChildCount() == 0) {
79                    continue;
80                }
81            } else if (child.getVisibility() != View.VISIBLE || !isOpaque(childBg)) {
82                // Potentially translucent or invisible children don't count, and we assume
83                // the content view will cover the whole area if we're in a background
84                // fallback situation.
85                continue;
86            }
87            left = Math.min(left, rootOffsetX + child.getLeft());
88            top = Math.min(top, rootOffsetY + child.getTop());
89            right = Math.max(right, rootOffsetX + child.getRight());
90            bottom = Math.max(bottom, rootOffsetY + child.getBottom());
91        }
92
93        // If one of the bar backgrounds is a solid color and covers the entire padding on a side
94        // we can drop that padding.
95        boolean eachBarCoversTopInY = true;
96        for (int i = 0; i < 2; i++) {
97            View v = (i == 0) ? coveringView1 : coveringView2;
98            if (v == null || v.getVisibility() != View.VISIBLE
99                    || v.getAlpha() != 1f || !isOpaque(v.getBackground())) {
100                eachBarCoversTopInY = false;
101                continue;
102            }
103
104            // Bar covers entire left padding
105            if (v.getTop() <= 0 && v.getBottom() >= height
106                    && v.getLeft() <= 0 && v.getRight() >= left) {
107                left = 0;
108            }
109            // Bar covers entire right padding
110            if (v.getTop() <= 0 && v.getBottom() >= height
111                    && v.getLeft() <= right && v.getRight() >= width) {
112                right = width;
113            }
114            // Bar covers entire top padding
115            if (v.getTop() <= 0 && v.getBottom() >= top
116                    && v.getLeft() <= 0 && v.getRight() >= width) {
117                top = 0;
118            }
119            // Bar covers entire bottom padding
120            if (v.getTop() <= bottom && v.getBottom() >= height
121                    && v.getLeft() <= 0 && v.getRight() >= width) {
122                bottom = height;
123            }
124
125            eachBarCoversTopInY &= v.getTop() <= 0 && v.getBottom() >= top;
126        }
127
128        // Special case: Sometimes, both covering views together may cover the top inset, but
129        // neither does on its own.
130        if (eachBarCoversTopInY && (viewsCoverEntireWidth(coveringView1, coveringView2, width)
131                || viewsCoverEntireWidth(coveringView2, coveringView1, width))) {
132            top = 0;
133        }
134
135        if (left >= right || top >= bottom) {
136            // No valid area to draw in.
137            return;
138        }
139
140        if (top > 0) {
141            mBackgroundFallback.setBounds(0, 0, width, top);
142            mBackgroundFallback.draw(c);
143        }
144        if (left > 0) {
145            mBackgroundFallback.setBounds(0, top, left, height);
146            mBackgroundFallback.draw(c);
147        }
148        if (right < width) {
149            mBackgroundFallback.setBounds(right, top, width, height);
150            mBackgroundFallback.draw(c);
151        }
152        if (bottom < height) {
153            mBackgroundFallback.setBounds(left, bottom, right, height);
154            mBackgroundFallback.draw(c);
155        }
156    }
157
158    private boolean isOpaque(Drawable childBg) {
159        return childBg != null && childBg.getOpacity() == PixelFormat.OPAQUE;
160    }
161
162    /**
163     * Returns true if {@code view1} starts before or on {@code 0} and extends at least
164     * up to {@code view2}, and that view extends at least to {@code width}.
165     *
166     * @param view1 the first view to check if it covers the width
167     * @param view2 the second view to check if it covers the width
168     * @param width the width to check for
169     * @return returns true if both views together cover the entire width (and view1 is to the left
170     *         of view2)
171     */
172    private boolean viewsCoverEntireWidth(View view1, View view2, int width) {
173        return view1.getLeft() <= 0
174                && view1.getRight() >= view2.getLeft()
175                && view2.getRight() >= width;
176    }
177}
178