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