• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package android.view;
18 
19 import com.android.internal.util.XmlUtils;
20 
21 import android.content.Context;
22 import android.content.res.Resources;
23 import android.content.res.TypedArray;
24 import android.content.res.XmlResourceParser;
25 import android.graphics.Bitmap;
26 import android.graphics.drawable.BitmapDrawable;
27 import android.graphics.drawable.Drawable;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 import 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  */
41 public 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 
PointerIcon(int style)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      */
getNullIcon()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      */
getDefaultIcon(Context context)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      */
getSystemIcon(Context context, int style)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, 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      */
createCustomIcon(Bitmap bitmap, float hotSpotX, float hotSpotY)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      */
loadCustomIcon(Resources resources, int resourceId)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(null, 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      */
load(Context context)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, 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      */
isNullIcon()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      */
isLoaded()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      */
getStyle()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      */
getBitmap()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      */
getHotSpotX()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      */
getHotSpotY()297     public float getHotSpotY() {
298         throwIfIconIsNotLoaded();
299         return mHotSpotY;
300     }
301 
throwIfIconIsNotLoaded()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 
describeContents()334     public int describeContents() {
335         return 0;
336     }
337 
writeToParcel(Parcel out, int flags)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
equals(Object other)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 
loadResource(Context context, Resources resources, int resourceId)376     private void loadResource(Context context, Resources resources, int resourceId) {
377         final 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             final 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.getDimension(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0);
388             hotSpotY = a.getDimension(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;
401         if (context == null) {
402             drawable = resources.getDrawable(bitmapRes);
403         } else {
404             drawable = context.getDrawable(bitmapRes);
405         }
406         if (!(drawable instanceof BitmapDrawable)) {
407             throw new IllegalArgumentException("<pointer-icon> bitmap attribute must "
408                     + "refer to a bitmap drawable.");
409         }
410 
411         // Set the properties now that we have successfully loaded the icon.
412         mBitmap = ((BitmapDrawable)drawable).getBitmap();
413         mHotSpotX = hotSpotX;
414         mHotSpotY = hotSpotY;
415     }
416 
validateHotSpot(Bitmap bitmap, float hotSpotX, float hotSpotY)417     private static void validateHotSpot(Bitmap bitmap, float hotSpotX, float hotSpotY) {
418         if (hotSpotX < 0 || hotSpotX >= bitmap.getWidth()) {
419             throw new IllegalArgumentException("x hotspot lies outside of the bitmap area");
420         }
421         if (hotSpotY < 0 || hotSpotY >= bitmap.getHeight()) {
422             throw new IllegalArgumentException("y hotspot lies outside of the bitmap area");
423         }
424     }
425 
getSystemIconStyleIndex(int style)426     private static int getSystemIconStyleIndex(int style) {
427         switch (style) {
428             case STYLE_ARROW:
429                 return com.android.internal.R.styleable.Pointer_pointerIconArrow;
430             case STYLE_SPOT_HOVER:
431                 return com.android.internal.R.styleable.Pointer_pointerIconSpotHover;
432             case STYLE_SPOT_TOUCH:
433                 return com.android.internal.R.styleable.Pointer_pointerIconSpotTouch;
434             case STYLE_SPOT_ANCHOR:
435                 return com.android.internal.R.styleable.Pointer_pointerIconSpotAnchor;
436             default:
437                 return 0;
438         }
439     }
440 }
441