1 /* 2 * Copyright (C) 2018 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 package com.android.systemui.plugins; 18 19 import android.graphics.Color; 20 import android.graphics.Rect; 21 import android.view.View; 22 23 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; 24 import com.android.systemui.plugins.annotations.DependsOn; 25 import com.android.systemui.plugins.annotations.ProvidesInterface; 26 27 import java.util.ArrayList; 28 import java.util.Collection; 29 30 /** 31 * Dispatches events to {@link DarkReceiver}s about changes in darkness, tint area and dark 32 * intensity. Accessible through {@link PluginDependency} 33 */ 34 @ProvidesInterface(version = DarkIconDispatcher.VERSION) 35 @DependsOn(target = DarkReceiver.class) 36 public interface DarkIconDispatcher { 37 int VERSION = 2; 38 39 /** 40 * Sets the dark area so {@link #applyDark} only affects the icons in the specified area. 41 * 42 * @param r the areas in which icons should change its tint, in logical screen 43 * coordinates 44 */ setIconsDarkArea(ArrayList<Rect> r)45 void setIconsDarkArea(ArrayList<Rect> r); 46 47 /** 48 * Adds a receiver to receive callbacks onDarkChanged 49 */ addDarkReceiver(DarkReceiver receiver)50 void addDarkReceiver(DarkReceiver receiver); 51 52 /** 53 * Must have been previously been added through one of the addDarkReceive methods above. 54 */ removeDarkReceiver(DarkReceiver object)55 void removeDarkReceiver(DarkReceiver object); 56 57 /** 58 * Used to reapply darkness on an object, must have previously been added through 59 * addDarkReceiver. 60 */ applyDark(DarkReceiver object)61 void applyDark(DarkReceiver object); 62 63 /** The default tint (applicable for dark backgrounds) is white */ 64 int DEFAULT_ICON_TINT = Color.WHITE; 65 /** To support an icon which wants to create contrast, the default tint is black-on-white. */ 66 int DEFAULT_INVERSE_ICON_TINT = Color.BLACK; 67 68 Rect sTmpRect = new Rect(); 69 int[] sTmpInt2 = new int[2]; 70 71 /** 72 * @return the tint to apply to view depending on the desired tint color and 73 * the screen tintArea in which to apply that tint 74 */ getTint(Collection<Rect> tintAreas, View view, int color)75 static int getTint(Collection<Rect> tintAreas, View view, int color) { 76 if (isInAreas(tintAreas, view)) { 77 return color; 78 } else { 79 return DEFAULT_ICON_TINT; 80 } 81 } 82 83 /** 84 * @return the tint to apply to a foreground, given that the background is tinted 85 * per {@link #getTint} 86 */ getInverseTint(Collection<Rect> tintAreas, View view, int inverseColor)87 static int getInverseTint(Collection<Rect> tintAreas, View view, int inverseColor) { 88 if (isInAreas(tintAreas, view)) { 89 return inverseColor; 90 } else { 91 return DEFAULT_INVERSE_ICON_TINT; 92 } 93 } 94 95 /** 96 * @return true if more than half of the view's area is in any of the given area Rects, false 97 * otherwise 98 */ isInAreas(Collection<Rect> areas, View view)99 static boolean isInAreas(Collection<Rect> areas, View view) { 100 if (areas.isEmpty()) { 101 return true; 102 } 103 for (Rect area : areas) { 104 if (isInArea(area, view)) { 105 return true; 106 } 107 } 108 return false; 109 } 110 111 /** 112 * @return true if more than half of the viewBounds are in any of the given area Rects, false 113 * otherwise 114 */ isInAreas(Collection<Rect> areas, Rect viewBounds)115 static boolean isInAreas(Collection<Rect> areas, Rect viewBounds) { 116 if (areas.isEmpty()) { 117 return true; 118 } 119 for (Rect area : areas) { 120 if (isInArea(area, viewBounds)) { 121 return true; 122 } 123 } 124 return false; 125 } 126 127 /** @return true if more than half of the viewBounds are in the area Rect, false otherwise */ isInArea(Rect area, Rect viewBounds)128 static boolean isInArea(Rect area, Rect viewBounds) { 129 if (area.isEmpty()) { 130 return true; 131 } 132 sTmpRect.set(area); 133 int left = viewBounds.left; 134 int width = viewBounds.width(); 135 136 int intersectStart = Math.max(left, area.left); 137 int intersectEnd = Math.min(left + width, area.right); 138 int intersectAmount = Math.max(0, intersectEnd - intersectStart); 139 140 boolean coversFullStatusBar = area.top <= 0; 141 boolean majorityOfWidth = 2 * intersectAmount > width; 142 return majorityOfWidth && coversFullStatusBar; 143 } 144 145 /** @return true if more than half of the view's area is in the area Rect, false otherwise */ isInArea(Rect area, View view)146 static boolean isInArea(Rect area, View view) { 147 if (area.isEmpty()) { 148 return true; 149 } 150 sTmpRect.set(area); 151 view.getLocationOnScreen(sTmpInt2); 152 int left = sTmpInt2[0]; 153 154 int intersectStart = Math.max(left, area.left); 155 int intersectEnd = Math.min(left + view.getWidth(), area.right); 156 int intersectAmount = Math.max(0, intersectEnd - intersectStart); 157 158 boolean coversFullStatusBar = area.top <= 0; 159 boolean majorityOfWidth = 2 * intersectAmount > view.getWidth(); 160 return majorityOfWidth && coversFullStatusBar; 161 } 162 163 /** 164 * Receives a callback on darkness changes 165 */ 166 @ProvidesInterface(version = DarkReceiver.VERSION) 167 interface DarkReceiver { 168 int VERSION = 3; 169 170 /** 171 * @param areas list of regions on screen where the tint applies 172 * @param darkIntensity float representing the level of tint. In the range [0,1] 173 * @param tint the tint applicable as a foreground contrast to the dark regions. This value 174 * is interpolated between a default light and dark tone, and is therefore 175 * usable as-is, as long as the view is in one of the areas defined in 176 * {@code areas}. 177 * 178 * @see DarkIconDispatcher#isInArea(Rect, View) for utilizing {@code areas} 179 * 180 * Note: only one of {@link #onDarkChanged(ArrayList, float, int)} or 181 * {@link #onDarkChangedWithContrast(ArrayList, int, int)} need to be implemented, as both 182 * will be called in the same circumstances. 183 */ onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint)184 void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint); 185 186 /** 187 * New version of onDarkChanged, which describes a tint plus an optional contrastTint 188 * that can be used if the tint is applied to the background of an icon. 189 * 190 * We use the 2 here to avoid the case where an existing override of onDarkChanged 191 * might pass in parameters as bare numbers (e.g. 0 instead of 0f) which might get 192 * mistakenly cast to (int) and therefore trigger this method. 193 * 194 * @param areas list of areas where dark tint applies 195 * @param tint int describing the tint color to use 196 * @param contrastTint if desired, a contrasting color that can be used for a foreground 197 * 198 * Note: only one of {@link #onDarkChanged(ArrayList, float, int)} or 199 * {@link #onDarkChangedWithContrast(ArrayList, int, int)} need to be implemented, as both 200 * will be called in the same circumstances. 201 */ onDarkChangedWithContrast(ArrayList<Rect> areas, int tint, int contrastTint)202 default void onDarkChangedWithContrast(ArrayList<Rect> areas, int tint, int contrastTint) {} 203 } 204 } 205