1 /* 2 * Copyright (C) 2007 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 android.util; 18 19 import android.ravenwood.annotation.RavenwoodKeepWholeClass; 20 21 import com.android.internal.R; 22 23 /** 24 * State sets are arrays of positive ints where each element 25 * represents the state of a {@link android.view.View} (e.g. focused, 26 * selected, visible, etc.). A {@link android.view.View} may be in 27 * one or more of those states. 28 * 29 * A state spec is an array of signed ints where each element 30 * represents a required (if positive) or an undesired (if negative) 31 * {@link android.view.View} state. 32 * 33 * Utils dealing with state sets. 34 * 35 * In theory we could encapsulate the state set and state spec arrays 36 * and not have static methods here but there is some concern about 37 * performance since these methods are called during view drawing. 38 */ 39 @RavenwoodKeepWholeClass 40 public class StateSet { 41 /** 42 * The order here is very important to 43 * {@link android.view.View#getDrawableState()} 44 */ 45 private static final int[][] VIEW_STATE_SETS; 46 47 /** @hide */ 48 public static final int VIEW_STATE_WINDOW_FOCUSED = 1; 49 /** @hide */ 50 public static final int VIEW_STATE_SELECTED = 1 << 1; 51 /** @hide */ 52 public static final int VIEW_STATE_FOCUSED = 1 << 2; 53 /** @hide */ 54 public static final int VIEW_STATE_ENABLED = 1 << 3; 55 /** @hide */ 56 public static final int VIEW_STATE_PRESSED = 1 << 4; 57 /** @hide */ 58 public static final int VIEW_STATE_ACTIVATED = 1 << 5; 59 /** @hide */ 60 public static final int VIEW_STATE_ACCELERATED = 1 << 6; 61 /** @hide */ 62 public static final int VIEW_STATE_HOVERED = 1 << 7; 63 /** @hide */ 64 public static final int VIEW_STATE_DRAG_CAN_ACCEPT = 1 << 8; 65 /** @hide */ 66 public static final int VIEW_STATE_DRAG_HOVERED = 1 << 9; 67 68 static final int[] VIEW_STATE_IDS = new int[] { 69 R.attr.state_window_focused, VIEW_STATE_WINDOW_FOCUSED, 70 R.attr.state_selected, VIEW_STATE_SELECTED, 71 R.attr.state_focused, VIEW_STATE_FOCUSED, 72 R.attr.state_enabled, VIEW_STATE_ENABLED, 73 R.attr.state_pressed, VIEW_STATE_PRESSED, 74 R.attr.state_activated, VIEW_STATE_ACTIVATED, 75 R.attr.state_accelerated, VIEW_STATE_ACCELERATED, 76 R.attr.state_hovered, VIEW_STATE_HOVERED, 77 R.attr.state_drag_can_accept, VIEW_STATE_DRAG_CAN_ACCEPT, 78 R.attr.state_drag_hovered, VIEW_STATE_DRAG_HOVERED 79 }; 80 81 static { 82 if ((VIEW_STATE_IDS.length / 2) != R.styleable.ViewDrawableStates.length) { 83 throw new IllegalStateException( 84 "VIEW_STATE_IDs array length does not match ViewDrawableStates style array"); 85 } 86 87 final int[] orderedIds = new int[VIEW_STATE_IDS.length]; 88 for (int i = 0; i < R.styleable.ViewDrawableStates.length; i++) { 89 final int viewState = R.styleable.ViewDrawableStates[i]; 90 for (int j = 0; j < VIEW_STATE_IDS.length; j += 2) { 91 if (VIEW_STATE_IDS[j] == viewState) { 92 orderedIds[i * 2] = viewState; 93 orderedIds[i * 2 + 1] = VIEW_STATE_IDS[j + 1]; 94 } 95 } 96 } 97 98 final int NUM_BITS = VIEW_STATE_IDS.length / 2; 99 VIEW_STATE_SETS = new int[1 << NUM_BITS][]; 100 for (int i = 0; i < VIEW_STATE_SETS.length; i++) { 101 final int numBits = Integer.bitCount(i); 102 final int[] set = new int[numBits]; 103 int pos = 0; 104 for (int j = 0; j < orderedIds.length; j += 2) { 105 if ((i & orderedIds[j + 1]) != 0) { 106 set[pos++] = orderedIds[j]; 107 } 108 } 109 VIEW_STATE_SETS[i] = set; 110 } 111 } 112 113 /** @hide */ get(int mask)114 public static int[] get(int mask) { 115 if (mask >= VIEW_STATE_SETS.length) { 116 throw new IllegalArgumentException("Invalid state set mask"); 117 } 118 return VIEW_STATE_SETS[mask]; 119 } 120 121 /** @hide */ StateSet()122 public StateSet() {} 123 124 /** 125 * A state specification that will be matched by all StateSets. 126 */ 127 public static final int[] WILD_CARD = new int[0]; 128 129 /** 130 * A state set that does not contain any valid states. 131 */ 132 public static final int[] NOTHING = new int[] { 0 }; 133 134 /** 135 * Return whether the stateSetOrSpec is matched by all StateSets. 136 * 137 * @param stateSetOrSpec a state set or state spec. 138 */ isWildCard(int[] stateSetOrSpec)139 public static boolean isWildCard(int[] stateSetOrSpec) { 140 return stateSetOrSpec.length == 0 || stateSetOrSpec[0] == 0; 141 } 142 143 /** 144 * Return whether the stateSet matches the desired stateSpec. 145 * 146 * @param stateSpec an array of required (if positive) or 147 * prohibited (if negative) {@link android.view.View} states. 148 * @param stateSet an array of {@link android.view.View} states 149 */ stateSetMatches(int[] stateSpec, int[] stateSet)150 public static boolean stateSetMatches(int[] stateSpec, int[] stateSet) { 151 if (stateSet == null) { 152 return (stateSpec == null || isWildCard(stateSpec)); 153 } 154 int stateSpecSize = stateSpec.length; 155 int stateSetSize = stateSet.length; 156 for (int i = 0; i < stateSpecSize; i++) { 157 int stateSpecState = stateSpec[i]; 158 if (stateSpecState == 0) { 159 // We've reached the end of the cases to match against. 160 return true; 161 } 162 final boolean mustMatch; 163 if (stateSpecState > 0) { 164 mustMatch = true; 165 } else { 166 // We use negative values to indicate must-NOT-match states. 167 mustMatch = false; 168 stateSpecState = -stateSpecState; 169 } 170 boolean found = false; 171 for (int j = 0; j < stateSetSize; j++) { 172 final int state = stateSet[j]; 173 if (state == 0) { 174 // We've reached the end of states to match. 175 if (mustMatch) { 176 // We didn't find this must-match state. 177 return false; 178 } else { 179 // Continue checking other must-not-match states. 180 break; 181 } 182 } 183 if (state == stateSpecState) { 184 if (mustMatch) { 185 found = true; 186 // Continue checking other other must-match states. 187 break; 188 } else { 189 // Any match of a must-not-match state returns false. 190 return false; 191 } 192 } 193 } 194 if (mustMatch && !found) { 195 // We've reached the end of states to match and we didn't 196 // find a must-match state. 197 return false; 198 } 199 } 200 return true; 201 } 202 203 /** 204 * Return whether the state matches the desired stateSpec. 205 * 206 * @param stateSpec an array of required (if positive) or 207 * prohibited (if negative) {@link android.view.View} states. 208 * @param state a {@link android.view.View} state 209 */ stateSetMatches(int[] stateSpec, int state)210 public static boolean stateSetMatches(int[] stateSpec, int state) { 211 int stateSpecSize = stateSpec.length; 212 for (int i = 0; i < stateSpecSize; i++) { 213 int stateSpecState = stateSpec[i]; 214 if (stateSpecState == 0) { 215 // We've reached the end of the cases to match against. 216 return true; 217 } 218 if (stateSpecState > 0) { 219 if (state != stateSpecState) { 220 return false; 221 } 222 } else { 223 // We use negative values to indicate must-NOT-match states. 224 if (state == -stateSpecState) { 225 // We matched a must-not-match case. 226 return false; 227 } 228 } 229 } 230 return true; 231 } 232 233 /** 234 * Check whether a list of state specs has an attribute specified. 235 * @param stateSpecs a list of state specs we're checking. 236 * @param attr an attribute we're looking for. 237 * @return {@code true} if the attribute is contained in the state specs. 238 * @hide 239 */ containsAttribute(int[][] stateSpecs, int attr)240 public static boolean containsAttribute(int[][] stateSpecs, int attr) { 241 if (stateSpecs != null) { 242 for (int[] spec : stateSpecs) { 243 if (spec == null) { 244 break; 245 } 246 for (int specAttr : spec) { 247 if (specAttr == attr || -specAttr == attr) { 248 return true; 249 } 250 } 251 } 252 } 253 return false; 254 } 255 trimStateSet(int[] states, int newSize)256 public static int[] trimStateSet(int[] states, int newSize) { 257 if (states.length == newSize) { 258 return states; 259 } 260 261 int[] trimmedStates = new int[newSize]; 262 System.arraycopy(states, 0, trimmedStates, 0, newSize); 263 return trimmedStates; 264 } 265 dump(int[] states)266 public static String dump(int[] states) { 267 StringBuilder sb = new StringBuilder(); 268 269 int count = states.length; 270 for (int i = 0; i < count; i++) { 271 272 switch (states[i]) { 273 case R.attr.state_window_focused: 274 sb.append("W "); 275 break; 276 case R.attr.state_pressed: 277 sb.append("P "); 278 break; 279 case R.attr.state_selected: 280 sb.append("S "); 281 break; 282 case R.attr.state_focused: 283 sb.append("F "); 284 break; 285 case R.attr.state_enabled: 286 sb.append("E "); 287 break; 288 case R.attr.state_checked: 289 sb.append("C "); 290 break; 291 case R.attr.state_activated: 292 sb.append("A "); 293 break; 294 case R.attr.state_hovered: 295 sb.append("H "); 296 break; 297 } 298 } 299 300 return sb.toString(); 301 } 302 } 303