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