PointerIcon.java revision 19a560197950425f7e1856d5bd1216fbc680bf70
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 17package android.view; 18 19import android.os.UserHandle; 20import android.provider.Settings; 21import com.android.internal.util.XmlUtils; 22 23import android.annotation.XmlRes; 24import android.content.Context; 25import android.content.res.Resources; 26import android.content.res.TypedArray; 27import android.content.res.XmlResourceParser; 28import android.graphics.Bitmap; 29import android.graphics.drawable.AnimationDrawable; 30import android.graphics.drawable.BitmapDrawable; 31import android.graphics.drawable.Drawable; 32import android.os.Parcel; 33import android.os.Parcelable; 34import android.util.Log; 35 36/** 37 * Represents an icon that can be used as a mouse pointer. 38 * <p> 39 * Pointer icons can be provided either by the system using system styles, 40 * or by applications using bitmaps or application resources. 41 * </p> 42 * 43 * @hide 44 */ 45public final class PointerIcon implements Parcelable { 46 private static final String TAG = "PointerIcon"; 47 48 /** Style constant: Custom icon with a user-supplied bitmap. */ 49 public static final int STYLE_CUSTOM = -1; 50 51 /** Style constant: Null icon. It has no bitmap. */ 52 public static final int STYLE_NULL = 0; 53 54 /** Style constant: no icons are specified. If all views uses this, then falls back 55 * to the default style, but this is helpful to distinguish a view explicitly want 56 * to have the default icon. 57 */ 58 public static final int STYLE_NOT_SPECIFIED = 1; 59 60 /** Style constant: Arrow icon. (Default mouse pointer) */ 61 public static final int STYLE_ARROW = 1000; 62 63 /** {@hide} Style constant: Spot hover icon for touchpads. */ 64 public static final int STYLE_SPOT_HOVER = 2000; 65 66 /** {@hide} Style constant: Spot touch icon for touchpads. */ 67 public static final int STYLE_SPOT_TOUCH = 2001; 68 69 /** {@hide} Style constant: Spot anchor icon for touchpads. */ 70 public static final int STYLE_SPOT_ANCHOR = 2002; 71 72 // Style constants for additional predefined icons for mice. 73 /** Style constant: context-menu. */ 74 public static final int STYLE_CONTEXT_MENU = 1001; 75 76 /** Style constant: hand. */ 77 public static final int STYLE_HAND = 1002; 78 79 /** Style constant: help. */ 80 public static final int STYLE_HELP = 1003; 81 82 /** Style constant: wait. */ 83 public static final int STYLE_WAIT = 1004; 84 85 /** Style constant: cell. */ 86 public static final int STYLE_CELL = 1006; 87 88 /** Style constant: crosshair. */ 89 public static final int STYLE_CROSSHAIR = 1007; 90 91 /** Style constant: text. */ 92 public static final int STYLE_TEXT = 1008; 93 94 /** Style constant: vertical-text. */ 95 public static final int STYLE_VERTICAL_TEXT = 1009; 96 97 /** Style constant: alias (indicating an alias of/shortcut to something is 98 * to be created. */ 99 public static final int STYLE_ALIAS = 1010; 100 101 /** Style constant: copy. */ 102 public static final int STYLE_COPY = 1011; 103 104 /** Style constant: no-drop. */ 105 public static final int STYLE_NO_DROP = 1012; 106 107 /** Style constant: all-scroll. */ 108 public static final int STYLE_ALL_SCROLL = 1013; 109 110 /** Style constant: horizontal double arrow mainly for resizing. */ 111 public static final int STYLE_HORIZONTAL_DOUBLE_ARROW = 1014; 112 113 /** Style constant: vertical double arrow mainly for resizing. */ 114 public static final int STYLE_VERTICAL_DOUBLE_ARROW = 1015; 115 116 /** Style constant: diagonal double arrow -- top-right to bottom-left. */ 117 public static final int STYLE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW = 1016; 118 119 /** Style constant: diagonal double arrow -- top-left to bottom-right. */ 120 public static final int STYLE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW = 1017; 121 122 /** Style constant: zoom-in. */ 123 public static final int STYLE_ZOOM_IN = 1018; 124 125 /** Style constant: zoom-out. */ 126 public static final int STYLE_ZOOM_OUT = 1019; 127 128 /** Style constant: grab. */ 129 public static final int STYLE_GRAB = 1020; 130 131 /** Style constant: grabbing. */ 132 public static final int STYLE_GRABBING = 1021; 133 134 // OEM private styles should be defined starting at this range to avoid 135 // conflicts with any system styles that may be defined in the future. 136 private static final int STYLE_OEM_FIRST = 10000; 137 138 /** {@hide} The default pointer icon. */ 139 public static final int STYLE_DEFAULT = STYLE_ARROW; 140 141 private static final PointerIcon gNullIcon = new PointerIcon(STYLE_NULL); 142 143 private final int mStyle; 144 private int mSystemIconResourceId; 145 private Bitmap mBitmap; 146 private float mHotSpotX; 147 private float mHotSpotY; 148 // The bitmaps for the additional frame of animated pointer icon. Note that the first frame 149 // will be stored in mBitmap. 150 private Bitmap mBitmapFrames[]; 151 private int mDurationPerFrame; 152 153 private PointerIcon(int style) { 154 mStyle = style; 155 } 156 157 /** 158 * Gets a special pointer icon that has no bitmap. 159 * 160 * @return The null pointer icon. 161 * 162 * @see #STYLE_NULL 163 */ 164 public static PointerIcon getNullIcon() { 165 return gNullIcon; 166 } 167 168 /** 169 * Gets the default pointer icon. 170 * 171 * @param context The context. 172 * @return The default pointer icon. 173 * 174 * @throws IllegalArgumentException if context is null. 175 */ 176 public static PointerIcon getDefaultIcon(Context context) { 177 return getSystemIcon(context, STYLE_DEFAULT); 178 } 179 180 /** 181 * Gets a system pointer icon for the given style. 182 * If style is not recognized, returns the default pointer icon. 183 * 184 * @param context The context. 185 * @param style The pointer icon style. 186 * @return The pointer icon. 187 * 188 * @throws IllegalArgumentException if context is null. 189 */ 190 public static PointerIcon getSystemIcon(Context context, int style) { 191 if (context == null) { 192 throw new IllegalArgumentException("context must not be null"); 193 } 194 195 if (style == STYLE_NULL) { 196 return gNullIcon; 197 } 198 199 int styleIndex = getSystemIconStyleIndex(style); 200 if (styleIndex == 0) { 201 styleIndex = getSystemIconStyleIndex(STYLE_DEFAULT); 202 } 203 204 int accessibilityConfig = Settings.Secure.getIntForUser( 205 context.getContentResolver(), Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON, 206 0, UserHandle.USER_CURRENT); 207 int defStyle = (accessibilityConfig == 1) ? 208 com.android.internal.R.style.LargePointer : com.android.internal.R.style.Pointer; 209 TypedArray a = context.obtainStyledAttributes(null, 210 com.android.internal.R.styleable.Pointer, 211 0, defStyle); 212 int resourceId = a.getResourceId(styleIndex, -1); 213 a.recycle(); 214 215 if (resourceId == -1) { 216 Log.w(TAG, "Missing theme resources for pointer icon style " + style); 217 return style == STYLE_DEFAULT ? gNullIcon : getSystemIcon(context, STYLE_DEFAULT); 218 } 219 220 PointerIcon icon = new PointerIcon(style); 221 if ((resourceId & 0xff000000) == 0x01000000) { 222 icon.mSystemIconResourceId = resourceId; 223 } else { 224 icon.loadResource(context, context.getResources(), resourceId); 225 } 226 return icon; 227 } 228 229 /** 230 * Creates a custom pointer from the given bitmap and hotspot information. 231 * 232 * @param bitmap The bitmap for the icon. 233 * @param hotSpotX The X offset of the pointer icon hotspot in the bitmap. 234 * Must be within the [0, bitmap.getWidth()) range. 235 * @param hotSpotY The Y offset of the pointer icon hotspot in the bitmap. 236 * Must be within the [0, bitmap.getHeight()) range. 237 * @return A pointer icon for this bitmap. 238 * 239 * @throws IllegalArgumentException if bitmap is null, or if the x/y hotspot 240 * parameters are invalid. 241 */ 242 public static PointerIcon createCustomIcon(Bitmap bitmap, float hotSpotX, float hotSpotY) { 243 if (bitmap == null) { 244 throw new IllegalArgumentException("bitmap must not be null"); 245 } 246 validateHotSpot(bitmap, hotSpotX, hotSpotY); 247 248 PointerIcon icon = new PointerIcon(STYLE_CUSTOM); 249 icon.mBitmap = bitmap; 250 icon.mHotSpotX = hotSpotX; 251 icon.mHotSpotY = hotSpotY; 252 return icon; 253 } 254 255 /** 256 * Loads a custom pointer icon from an XML resource. 257 * <p> 258 * The XML resource should have the following form: 259 * <code> 260 * <?xml version="1.0" encoding="utf-8"?> 261 * <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" 262 * android:bitmap="@drawable/my_pointer_bitmap" 263 * android:hotSpotX="24" 264 * android:hotSpotY="24" /> 265 * </code> 266 * </p> 267 * 268 * @param resources The resources object. 269 * @param resourceId The resource id. 270 * @return The pointer icon. 271 * 272 * @throws IllegalArgumentException if resources is null. 273 * @throws Resources.NotFoundException if the resource was not found or the drawable 274 * linked in the resource was not found. 275 */ 276 public static PointerIcon loadCustomIcon(Resources resources, @XmlRes int resourceId) { 277 if (resources == null) { 278 throw new IllegalArgumentException("resources must not be null"); 279 } 280 281 PointerIcon icon = new PointerIcon(STYLE_CUSTOM); 282 icon.loadResource(null, resources, resourceId); 283 return icon; 284 } 285 286 /** 287 * Loads the bitmap and hotspot information for a pointer icon, if it is not already loaded. 288 * Returns a pointer icon (not necessarily the same instance) with the information filled in. 289 * 290 * @param context The context. 291 * @return The loaded pointer icon. 292 * 293 * @throws IllegalArgumentException if context is null. 294 * @see #isLoaded() 295 * @hide 296 */ 297 public PointerIcon load(Context context) { 298 if (context == null) { 299 throw new IllegalArgumentException("context must not be null"); 300 } 301 302 if (mSystemIconResourceId == 0 || mBitmap != null) { 303 return this; 304 } 305 306 PointerIcon result = new PointerIcon(mStyle); 307 result.mSystemIconResourceId = mSystemIconResourceId; 308 result.loadResource(context, context.getResources(), mSystemIconResourceId); 309 return result; 310 } 311 312 /** 313 * Returns true if the pointer icon style is {@link #STYLE_NULL}. 314 * 315 * @return True if the pointer icon style is {@link #STYLE_NULL}. 316 */ 317 public boolean isNullIcon() { 318 return mStyle == STYLE_NULL; 319 } 320 321 /** 322 * Returns true if the pointer icon has been loaded and its bitmap and hotspot 323 * information are available. 324 * 325 * @return True if the pointer icon is loaded. 326 * @see #load(Context) 327 */ 328 public boolean isLoaded() { 329 return mBitmap != null || mStyle == STYLE_NULL; 330 } 331 332 /** 333 * Gets the style of the pointer icon. 334 * 335 * @return The pointer icon style. 336 */ 337 public int getStyle() { 338 return mStyle; 339 } 340 341 /** 342 * Gets the bitmap of the pointer icon. 343 * 344 * @return The pointer icon bitmap, or null if the style is {@link #STYLE_NULL}. 345 * 346 * @throws IllegalStateException if the bitmap is not loaded. 347 * @see #isLoaded() 348 * @see #load(Context) 349 */ 350 public Bitmap getBitmap() { 351 throwIfIconIsNotLoaded(); 352 return mBitmap; 353 } 354 355 /** 356 * Gets the X offset of the pointer icon hotspot. 357 * 358 * @return The hotspot X offset. 359 * 360 * @throws IllegalStateException if the bitmap is not loaded. 361 * @see #isLoaded() 362 * @see #load(Context) 363 */ 364 public float getHotSpotX() { 365 throwIfIconIsNotLoaded(); 366 return mHotSpotX; 367 } 368 369 /** 370 * Gets the Y offset of the pointer icon hotspot. 371 * 372 * @return The hotspot Y offset. 373 * 374 * @throws IllegalStateException if the bitmap is not loaded. 375 * @see #isLoaded() 376 * @see #load(Context) 377 */ 378 public float getHotSpotY() { 379 throwIfIconIsNotLoaded(); 380 return mHotSpotY; 381 } 382 383 private void throwIfIconIsNotLoaded() { 384 if (!isLoaded()) { 385 throw new IllegalStateException("The icon is not loaded."); 386 } 387 } 388 389 public static final Parcelable.Creator<PointerIcon> CREATOR 390 = new Parcelable.Creator<PointerIcon>() { 391 public PointerIcon createFromParcel(Parcel in) { 392 int style = in.readInt(); 393 if (style == STYLE_NULL) { 394 return getNullIcon(); 395 } 396 397 int systemIconResourceId = in.readInt(); 398 if (systemIconResourceId != 0) { 399 PointerIcon icon = new PointerIcon(style); 400 icon.mSystemIconResourceId = systemIconResourceId; 401 return icon; 402 } 403 404 Bitmap bitmap = Bitmap.CREATOR.createFromParcel(in); 405 float hotSpotX = in.readFloat(); 406 float hotSpotY = in.readFloat(); 407 return PointerIcon.createCustomIcon(bitmap, hotSpotX, hotSpotY); 408 } 409 410 public PointerIcon[] newArray(int size) { 411 return new PointerIcon[size]; 412 } 413 }; 414 415 public int describeContents() { 416 return 0; 417 } 418 419 public void writeToParcel(Parcel out, int flags) { 420 out.writeInt(mStyle); 421 422 if (mStyle != STYLE_NULL) { 423 out.writeInt(mSystemIconResourceId); 424 if (mSystemIconResourceId == 0) { 425 mBitmap.writeToParcel(out, flags); 426 out.writeFloat(mHotSpotX); 427 out.writeFloat(mHotSpotY); 428 } 429 } 430 } 431 432 @Override 433 public boolean equals(Object other) { 434 if (this == other) { 435 return true; 436 } 437 438 if (other == null || !(other instanceof PointerIcon)) { 439 return false; 440 } 441 442 PointerIcon otherIcon = (PointerIcon) other; 443 if (mStyle != otherIcon.mStyle 444 || mSystemIconResourceId != otherIcon.mSystemIconResourceId) { 445 return false; 446 } 447 448 if (mSystemIconResourceId == 0 && (mBitmap != otherIcon.mBitmap 449 || mHotSpotX != otherIcon.mHotSpotX 450 || mHotSpotY != otherIcon.mHotSpotY)) { 451 return false; 452 } 453 454 return true; 455 } 456 457 private void loadResource(Context context, Resources resources, @XmlRes int resourceId) { 458 final XmlResourceParser parser = resources.getXml(resourceId); 459 final int bitmapRes; 460 final float hotSpotX; 461 final float hotSpotY; 462 try { 463 XmlUtils.beginDocument(parser, "pointer-icon"); 464 465 final TypedArray a = resources.obtainAttributes( 466 parser, com.android.internal.R.styleable.PointerIcon); 467 bitmapRes = a.getResourceId(com.android.internal.R.styleable.PointerIcon_bitmap, 0); 468 hotSpotX = a.getDimension(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0); 469 hotSpotY = a.getDimension(com.android.internal.R.styleable.PointerIcon_hotSpotY, 0); 470 a.recycle(); 471 } catch (Exception ex) { 472 throw new IllegalArgumentException("Exception parsing pointer icon resource.", ex); 473 } finally { 474 parser.close(); 475 } 476 477 if (bitmapRes == 0) { 478 throw new IllegalArgumentException("<pointer-icon> is missing bitmap attribute."); 479 } 480 481 Drawable drawable; 482 if (context == null) { 483 drawable = resources.getDrawable(bitmapRes); 484 } else { 485 drawable = context.getDrawable(bitmapRes); 486 } 487 if (drawable instanceof AnimationDrawable) { 488 // Extract animation frame bitmaps. 489 final AnimationDrawable animationDrawable = (AnimationDrawable) drawable; 490 final int frames = animationDrawable.getNumberOfFrames(); 491 drawable = animationDrawable.getFrame(0); 492 if (frames == 1) { 493 Log.w(TAG, "Animation icon with single frame -- simply treating the first " 494 + "frame as a normal bitmap icon."); 495 } else { 496 // Assumes they have the exact duration. 497 mDurationPerFrame = animationDrawable.getDuration(0); 498 mBitmapFrames = new Bitmap[frames - 1]; 499 final int width = drawable.getIntrinsicWidth(); 500 final int height = drawable.getIntrinsicHeight(); 501 for (int i = 1; i < frames; ++i) { 502 Drawable drawableFrame = animationDrawable.getFrame(i); 503 if (!(drawableFrame instanceof BitmapDrawable)) { 504 throw new IllegalArgumentException("Frame of an animated pointer icon " 505 + "must refer to a bitmap drawable."); 506 } 507 if (drawableFrame.getIntrinsicWidth() != width || 508 drawableFrame.getIntrinsicHeight() != height) { 509 throw new IllegalArgumentException("The bitmap size of " + i + "-th frame " 510 + "is different. All frames should have the exact same size and " 511 + "share the same hotspot."); 512 } 513 mBitmapFrames[i - 1] = ((BitmapDrawable)drawableFrame).getBitmap(); 514 } 515 } 516 } 517 if (!(drawable instanceof BitmapDrawable)) { 518 throw new IllegalArgumentException("<pointer-icon> bitmap attribute must " 519 + "refer to a bitmap drawable."); 520 } 521 522 // Set the properties now that we have successfully loaded the icon. 523 mBitmap = ((BitmapDrawable)drawable).getBitmap(); 524 mHotSpotX = hotSpotX; 525 mHotSpotY = hotSpotY; 526 } 527 528 private static void validateHotSpot(Bitmap bitmap, float hotSpotX, float hotSpotY) { 529 if (hotSpotX < 0 || hotSpotX >= bitmap.getWidth()) { 530 throw new IllegalArgumentException("x hotspot lies outside of the bitmap area"); 531 } 532 if (hotSpotY < 0 || hotSpotY >= bitmap.getHeight()) { 533 throw new IllegalArgumentException("y hotspot lies outside of the bitmap area"); 534 } 535 } 536 537 private static int getSystemIconStyleIndex(int style) { 538 switch (style) { 539 case STYLE_ARROW: 540 return com.android.internal.R.styleable.Pointer_pointerIconArrow; 541 case STYLE_SPOT_HOVER: 542 return com.android.internal.R.styleable.Pointer_pointerIconSpotHover; 543 case STYLE_SPOT_TOUCH: 544 return com.android.internal.R.styleable.Pointer_pointerIconSpotTouch; 545 case STYLE_SPOT_ANCHOR: 546 return com.android.internal.R.styleable.Pointer_pointerIconSpotAnchor; 547 case STYLE_HAND: 548 return com.android.internal.R.styleable.Pointer_pointerIconHand; 549 case STYLE_CONTEXT_MENU: 550 return com.android.internal.R.styleable.Pointer_pointerIconContextMenu; 551 case STYLE_HELP: 552 return com.android.internal.R.styleable.Pointer_pointerIconHelp; 553 case STYLE_WAIT: 554 return com.android.internal.R.styleable.Pointer_pointerIconWait; 555 case STYLE_CELL: 556 return com.android.internal.R.styleable.Pointer_pointerIconCell; 557 case STYLE_CROSSHAIR: 558 return com.android.internal.R.styleable.Pointer_pointerIconCrosshair; 559 case STYLE_TEXT: 560 return com.android.internal.R.styleable.Pointer_pointerIconText; 561 case STYLE_VERTICAL_TEXT: 562 return com.android.internal.R.styleable.Pointer_pointerIconVerticalText; 563 case STYLE_ALIAS: 564 return com.android.internal.R.styleable.Pointer_pointerIconAlias; 565 case STYLE_COPY: 566 return com.android.internal.R.styleable.Pointer_pointerIconCopy; 567 case STYLE_ALL_SCROLL: 568 return com.android.internal.R.styleable.Pointer_pointerIconAllScroll; 569 case STYLE_NO_DROP: 570 return com.android.internal.R.styleable.Pointer_pointerIconNodrop; 571 case STYLE_HORIZONTAL_DOUBLE_ARROW: 572 return com.android.internal.R.styleable.Pointer_pointerIconHorizontalDoubleArrow; 573 case STYLE_VERTICAL_DOUBLE_ARROW: 574 return com.android.internal.R.styleable.Pointer_pointerIconVerticalDoubleArrow; 575 case STYLE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW: 576 return com.android.internal.R.styleable. 577 Pointer_pointerIconTopRightDiagonalDoubleArrow; 578 case STYLE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW: 579 return com.android.internal.R.styleable. 580 Pointer_pointerIconTopLeftDiagonalDoubleArrow; 581 case STYLE_ZOOM_IN: 582 return com.android.internal.R.styleable.Pointer_pointerIconZoomIn; 583 case STYLE_ZOOM_OUT: 584 return com.android.internal.R.styleable.Pointer_pointerIconZoomOut; 585 case STYLE_GRAB: 586 return com.android.internal.R.styleable.Pointer_pointerIconGrab; 587 case STYLE_GRABBING: 588 return com.android.internal.R.styleable.Pointer_pointerIconGrabbing; 589 default: 590 return 0; 591 } 592 } 593} 594