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