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
17package android.util;
18
19import 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
38public 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