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