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