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 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 * <?xml version="1.0" encoding="utf-8"?> 180 * <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" /> 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 } 436