blob: bb7ed41a05fca6bba0a52b5d2bc8d36c225f0924 [file] [log] [blame]
Jeff Brown2352b972011-04-12 22:39:53 -07001/*
2 * Copyright (C) 2011 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.view;
18
19import com.android.internal.util.XmlUtils;
20
21import android.content.Context;
22import android.content.res.Resources;
23import android.content.res.TypedArray;
24import android.content.res.XmlResourceParser;
25import android.graphics.Bitmap;
26import android.graphics.drawable.BitmapDrawable;
27import android.graphics.drawable.Drawable;
28import android.os.Parcel;
29import android.os.Parcelable;
30import android.util.Log;
31
32/**
33 * Represents an icon that can be used as a mouse pointer.
34 * <p>
35 * Pointer icons can be provided either by the system using system styles,
36 * or by applications using bitmaps or application resources.
37 * </p>
38 *
39 * @hide
40 */
41public final class PointerIcon implements Parcelable {
42 private static final String TAG = "PointerIcon";
43
44 /** Style constant: Custom icon with a user-supplied bitmap. */
45 public static final int STYLE_CUSTOM = -1;
46
47 /** Style constant: Null icon. It has no bitmap. */
48 public static final int STYLE_NULL = 0;
49
50 /** Style constant: Arrow icon. (Default mouse pointer) */
51 public static final int STYLE_ARROW = 1000;
52
53 /** {@hide} Style constant: Spot hover icon for touchpads. */
54 public static final int STYLE_SPOT_HOVER = 2000;
55
56 /** {@hide} Style constant: Spot touch icon for touchpads. */
57 public static final int STYLE_SPOT_TOUCH = 2001;
58
59 /** {@hide} Style constant: Spot anchor icon for touchpads. */
60 public static final int STYLE_SPOT_ANCHOR = 2002;
61
62 // OEM private styles should be defined starting at this range to avoid
63 // conflicts with any system styles that may be defined in the future.
64 private static final int STYLE_OEM_FIRST = 10000;
65
66 // The default pointer icon.
67 private static final int STYLE_DEFAULT = STYLE_ARROW;
68
69 private static final PointerIcon gNullIcon = new PointerIcon(STYLE_NULL);
70
71 private final int mStyle;
72 private int mSystemIconResourceId;
73 private Bitmap mBitmap;
74 private float mHotSpotX;
75 private float mHotSpotY;
76
77 private PointerIcon(int style) {
78 mStyle = style;
79 }
80
81 /**
82 * Gets a special pointer icon that has no bitmap.
83 *
84 * @return The null pointer icon.
85 *
86 * @see #STYLE_NULL
87 */
88 public static PointerIcon getNullIcon() {
89 return gNullIcon;
90 }
91
92 /**
93 * Gets the default pointer icon.
94 *
95 * @param context The context.
96 * @return The default pointer icon.
97 *
98 * @throws IllegalArgumentException if context is null.
99 */
100 public static PointerIcon getDefaultIcon(Context context) {
101 return getSystemIcon(context, STYLE_DEFAULT);
102 }
103
104 /**
105 * Gets a system pointer icon for the given style.
106 * If style is not recognized, returns the default pointer icon.
107 *
108 * @param context The context.
109 * @param style The pointer icon style.
110 * @return The pointer icon.
111 *
112 * @throws IllegalArgumentException if context is null.
113 */
114 public static PointerIcon getSystemIcon(Context context, int style) {
115 if (context == null) {
116 throw new IllegalArgumentException("context must not be null");
117 }
118
119 if (style == STYLE_NULL) {
120 return gNullIcon;
121 }
122
123 int styleIndex = getSystemIconStyleIndex(style);
124 if (styleIndex == 0) {
125 styleIndex = getSystemIconStyleIndex(STYLE_DEFAULT);
126 }
127
128 TypedArray a = context.obtainStyledAttributes(null,
129 com.android.internal.R.styleable.Pointer,
130 com.android.internal.R.attr.pointerStyle, 0);
131 int resourceId = a.getResourceId(styleIndex, -1);
132 a.recycle();
133
134 if (resourceId == -1) {
135 Log.w(TAG, "Missing theme resources for pointer icon style " + style);
136 return style == STYLE_DEFAULT ? gNullIcon : getSystemIcon(context, STYLE_DEFAULT);
137 }
138
139 PointerIcon icon = new PointerIcon(style);
140 if ((resourceId & 0xff000000) == 0x01000000) {
141 icon.mSystemIconResourceId = resourceId;
142 } else {
143 icon.loadResource(context.getResources(), resourceId);
144 }
145 return icon;
146 }
147
148 /**
149 * Creates a custom pointer from the given bitmap and hotspot information.
150 *
151 * @param bitmap The bitmap for the icon.
152 * @param hotspotX The X offset of the pointer icon hotspot in the bitmap.
153 * Must be within the [0, bitmap.getWidth()) range.
154 * @param hotspotY The Y offset of the pointer icon hotspot in the bitmap.
155 * Must be within the [0, bitmap.getHeight()) range.
156 * @return A pointer icon for this bitmap.
157 *
158 * @throws IllegalArgumentException if bitmap is null, or if the x/y hotspot
159 * parameters are invalid.
160 */
161 public static PointerIcon createCustomIcon(Bitmap bitmap, float hotSpotX, float hotSpotY) {
162 if (bitmap == null) {
163 throw new IllegalArgumentException("bitmap must not be null");
164 }
165 validateHotSpot(bitmap, hotSpotX, hotSpotY);
166
167 PointerIcon icon = new PointerIcon(STYLE_CUSTOM);
168 icon.mBitmap = bitmap;
169 icon.mHotSpotX = hotSpotX;
170 icon.mHotSpotY = hotSpotY;
171 return icon;
172 }
173
174 /**
175 * Loads a custom pointer icon from an XML resource.
176 * <p>
177 * The XML resource should have the following form:
178 * <code>
179 * &lt;?xml version="1.0" encoding="utf-8"?&gt;
180 * &lt;pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
181 * android:bitmap="@drawable/my_pointer_bitmap"
182 * android:hotSpotX="24"
183 * android:hotSpotY="24" /&gt;
184 * </code>
185 * </p>
186 *
187 * @param resources The resources object.
188 * @param resourceId The resource id.
189 * @return The pointer icon.
190 *
191 * @throws IllegalArgumentException if resources is null.
192 * @throws Resources.NotFoundException if the resource was not found or the drawable
193 * linked in the resource was not found.
194 */
195 public static PointerIcon loadCustomIcon(Resources resources, int resourceId) {
196 if (resources == null) {
197 throw new IllegalArgumentException("resources must not be null");
198 }
199
200 PointerIcon icon = new PointerIcon(STYLE_CUSTOM);
201 icon.loadResource(resources, resourceId);
202 return icon;
203 }
204
205 /**
206 * Loads the bitmap and hotspot information for a pointer icon, if it is not already loaded.
207 * Returns a pointer icon (not necessarily the same instance) with the information filled in.
208 *
209 * @param context The context.
210 * @return The loaded pointer icon.
211 *
212 * @throws IllegalArgumentException if context is null.
213 * @see #isLoaded()
214 * @hide
215 */
216 public PointerIcon load(Context context) {
217 if (context == null) {
218 throw new IllegalArgumentException("context must not be null");
219 }
220
221 if (mSystemIconResourceId == 0 || mBitmap != null) {
222 return this;
223 }
224
225 PointerIcon result = new PointerIcon(mStyle);
226 result.mSystemIconResourceId = mSystemIconResourceId;
227 result.loadResource(context.getResources(), mSystemIconResourceId);
228 return result;
229 }
230
231 /**
232 * Returns true if the pointer icon style is {@link #STYLE_NULL}.
233 *
234 * @return True if the pointer icon style is {@link #STYLE_NULL}.
235 */
236 public boolean isNullIcon() {
237 return mStyle == STYLE_NULL;
238 }
239
240 /**
241 * Returns true if the pointer icon has been loaded and its bitmap and hotspot
242 * information are available.
243 *
244 * @return True if the pointer icon is loaded.
245 * @see #load(Context)
246 */
247 public boolean isLoaded() {
248 return mBitmap != null || mStyle == STYLE_NULL;
249 }
250
251 /**
252 * Gets the style of the pointer icon.
253 *
254 * @return The pointer icon style.
255 */
256 public int getStyle() {
257 return mStyle;
258 }
259
260 /**
261 * Gets the bitmap of the pointer icon.
262 *
263 * @return The pointer icon bitmap, or null if the style is {@link #STYLE_NULL}.
264 *
265 * @throws IllegalStateException if the bitmap is not loaded.
266 * @see #isLoaded()
267 * @see #load(Context)
268 */
269 public Bitmap getBitmap() {
270 throwIfIconIsNotLoaded();
271 return mBitmap;
272 }
273
274 /**
275 * Gets the X offset of the pointer icon hotspot.
276 *
277 * @return The hotspot X offset.
278 *
279 * @throws IllegalStateException if the bitmap is not loaded.
280 * @see #isLoaded()
281 * @see #load(Context)
282 */
283 public float getHotSpotX() {
284 throwIfIconIsNotLoaded();
285 return mHotSpotX;
286 }
287
288 /**
289 * Gets the Y offset of the pointer icon hotspot.
290 *
291 * @return The hotspot Y offset.
292 *
293 * @throws IllegalStateException if the bitmap is not loaded.
294 * @see #isLoaded()
295 * @see #load(Context)
296 */
297 public float getHotSpotY() {
298 throwIfIconIsNotLoaded();
299 return mHotSpotY;
300 }
301
302 private void throwIfIconIsNotLoaded() {
303 if (!isLoaded()) {
304 throw new IllegalStateException("The icon is not loaded.");
305 }
306 }
307
308 public static final Parcelable.Creator<PointerIcon> CREATOR
309 = new Parcelable.Creator<PointerIcon>() {
310 public PointerIcon createFromParcel(Parcel in) {
311 int style = in.readInt();
312 if (style == STYLE_NULL) {
313 return getNullIcon();
314 }
315
316 int systemIconResourceId = in.readInt();
317 if (systemIconResourceId != 0) {
318 PointerIcon icon = new PointerIcon(style);
319 icon.mSystemIconResourceId = systemIconResourceId;
320 return icon;
321 }
322
323 Bitmap bitmap = Bitmap.CREATOR.createFromParcel(in);
324 float hotSpotX = in.readFloat();
325 float hotSpotY = in.readFloat();
326 return PointerIcon.createCustomIcon(bitmap, hotSpotX, hotSpotY);
327 }
328
329 public PointerIcon[] newArray(int size) {
330 return new PointerIcon[size];
331 }
332 };
333
334 public int describeContents() {
335 return 0;
336 }
337
338 public void writeToParcel(Parcel out, int flags) {
339 out.writeInt(mStyle);
340
341 if (mStyle != STYLE_NULL) {
342 out.writeInt(mSystemIconResourceId);
343 if (mSystemIconResourceId == 0) {
344 mBitmap.writeToParcel(out, flags);
345 out.writeFloat(mHotSpotX);
346 out.writeFloat(mHotSpotY);
347 }
348 }
349 }
350
351 @Override
352 public boolean equals(Object other) {
353 if (this == other) {
354 return true;
355 }
356
357 if (other == null || !(other instanceof PointerIcon)) {
358 return false;
359 }
360
361 PointerIcon otherIcon = (PointerIcon) other;
362 if (mStyle != otherIcon.mStyle
363 || mSystemIconResourceId != otherIcon.mSystemIconResourceId) {
364 return false;
365 }
366
367 if (mSystemIconResourceId == 0 && (mBitmap != otherIcon.mBitmap
368 || mHotSpotX != otherIcon.mHotSpotX
369 || mHotSpotY != otherIcon.mHotSpotY)) {
370 return false;
371 }
372
373 return true;
374 }
375
376 private void loadResource(Resources resources, int resourceId) {
377 XmlResourceParser parser = resources.getXml(resourceId);
378 final int bitmapRes;
379 final float hotSpotX;
380 final float hotSpotY;
381 try {
382 XmlUtils.beginDocument(parser, "pointer-icon");
383
384 TypedArray a = resources.obtainAttributes(
385 parser, com.android.internal.R.styleable.PointerIcon);
386 bitmapRes = a.getResourceId(com.android.internal.R.styleable.PointerIcon_bitmap, 0);
387 hotSpotX = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0);
388 hotSpotY = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotY, 0);
389 a.recycle();
390 } catch (Exception ex) {
391 throw new IllegalArgumentException("Exception parsing pointer icon resource.", ex);
392 } finally {
393 parser.close();
394 }
395
396 if (bitmapRes == 0) {
397 throw new IllegalArgumentException("<pointer-icon> is missing bitmap attribute.");
398 }
399
400 Drawable drawable = resources.getDrawable(bitmapRes);
401 if (!(drawable instanceof BitmapDrawable)) {
402 throw new IllegalArgumentException("<pointer-icon> bitmap attribute must "
403 + "refer to a bitmap drawable.");
404 }
405
406 // Set the properties now that we have successfully loaded the icon.
407 mBitmap = ((BitmapDrawable)drawable).getBitmap();
408 mHotSpotX = hotSpotX;
409 mHotSpotY = hotSpotY;
410 }
411
412 private static void validateHotSpot(Bitmap bitmap, float hotSpotX, float hotSpotY) {
413 if (hotSpotX < 0 || hotSpotX >= bitmap.getWidth()) {
414 throw new IllegalArgumentException("x hotspot lies outside of the bitmap area");
415 }
416 if (hotSpotY < 0 || hotSpotY >= bitmap.getHeight()) {
417 throw new IllegalArgumentException("y hotspot lies outside of the bitmap area");
418 }
419 }
420
421 private static int getSystemIconStyleIndex(int style) {
422 switch (style) {
423 case STYLE_ARROW:
424 return com.android.internal.R.styleable.Pointer_pointerIconArrow;
425 case STYLE_SPOT_HOVER:
426 return com.android.internal.R.styleable.Pointer_pointerIconSpotHover;
427 case STYLE_SPOT_TOUCH:
428 return com.android.internal.R.styleable.Pointer_pointerIconSpotTouch;
429 case STYLE_SPOT_ANCHOR:
430 return com.android.internal.R.styleable.Pointer_pointerIconSpotAnchor;
431 default:
432 return 0;
433 }
434 }
435}