• 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 android.annotation.FlaggedApi;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.TestApi;
24 import android.annotation.XmlRes;
25 import android.compat.annotation.UnsupportedAppUsage;
26 import android.content.Context;
27 import android.content.res.Resources;
28 import android.content.res.TypedArray;
29 import android.content.res.XmlResourceParser;
30 import android.graphics.Bitmap;
31 import android.graphics.Canvas;
32 import android.graphics.Paint;
33 import android.graphics.Rect;
34 import android.graphics.RectF;
35 import android.graphics.drawable.AnimationDrawable;
36 import android.graphics.drawable.BitmapDrawable;
37 import android.graphics.drawable.Drawable;
38 import android.graphics.drawable.VectorDrawable;
39 import android.os.Build;
40 import android.os.Parcel;
41 import android.os.Parcelable;
42 import android.os.PointerIconType;
43 import android.util.Log;
44 import android.util.SparseArray;
45 import android.view.flags.Flags;
46 
47 import androidx.annotation.VisibleForTesting;
48 
49 import com.android.internal.util.XmlUtils;
50 
51 import java.lang.annotation.Retention;
52 import java.lang.annotation.RetentionPolicy;
53 
54 /**
55  * Represents an icon that can be used as a mouse pointer.
56  * <p>
57  * Pointer icons can be provided either by the system using system types,
58  * or by applications using bitmaps or application resources.
59  * </p>
60  */
61 public final class PointerIcon implements Parcelable {
62     private static final String TAG = "PointerIcon";
63 
64     /** {@hide} Type constant: Custom icon with a user-supplied bitmap. */
65     public static final int TYPE_CUSTOM = PointerIconType.CUSTOM;
66 
67     /** Type constant: Null icon.  It has no bitmap. */
68     public static final int TYPE_NULL = PointerIconType.TYPE_NULL;
69 
70     /**
71      * Type constant: no icons are specified. If all views uses this, then the pointer icon falls
72      * back to the default type, but this is helpful to distinguish a view that explicitly wants
73      * to have the default icon.
74      * @hide
75      */
76     public static final int TYPE_NOT_SPECIFIED = PointerIconType.NOT_SPECIFIED;
77 
78     /** Type constant: Arrow icon.  (Default mouse pointer) */
79     public static final int TYPE_ARROW = PointerIconType.ARROW;
80 
81     /** {@hide} Type constant: Spot hover icon for touchpads. */
82     public static final int TYPE_SPOT_HOVER = PointerIconType.SPOT_HOVER;
83 
84     /** {@hide} Type constant: Spot touch icon for touchpads. */
85     public static final int TYPE_SPOT_TOUCH = PointerIconType.SPOT_TOUCH;
86 
87     /** {@hide} Type constant: Spot anchor icon for touchpads. */
88     public static final int TYPE_SPOT_ANCHOR = PointerIconType.SPOT_ANCHOR;
89 
90     // Type constants for additional predefined icons for mice.
91     /** Type constant: context-menu. */
92     public static final int TYPE_CONTEXT_MENU = PointerIconType.CONTEXT_MENU;
93 
94     /** Type constant: hand. */
95     public static final int TYPE_HAND = PointerIconType.HAND;
96 
97     /** Type constant: help. */
98     public static final int TYPE_HELP = PointerIconType.HELP;
99 
100     /** Type constant: wait. */
101     public static final int TYPE_WAIT = PointerIconType.WAIT;
102 
103     /** Type constant: cell. */
104     public static final int TYPE_CELL = PointerIconType.CELL;
105 
106     /** Type constant: crosshair. */
107     public static final int TYPE_CROSSHAIR = PointerIconType.CROSSHAIR;
108 
109     /** Type constant: text. */
110     public static final int TYPE_TEXT = PointerIconType.TEXT;
111 
112     /** Type constant: vertical-text. */
113     public static final int TYPE_VERTICAL_TEXT = PointerIconType.VERTICAL_TEXT;
114 
115     /** Type constant: alias (indicating an alias of/shortcut to something is
116       * to be created. */
117     public static final int TYPE_ALIAS = PointerIconType.ALIAS;
118 
119     /** Type constant: copy. */
120     public static final int TYPE_COPY = PointerIconType.COPY;
121 
122     /** Type constant: no-drop. */
123     public static final int TYPE_NO_DROP = PointerIconType.NO_DROP;
124 
125     /** Type constant: all-scroll. */
126     public static final int TYPE_ALL_SCROLL = PointerIconType.ALL_SCROLL;
127 
128     /** Type constant: horizontal double arrow mainly for resizing. */
129     public static final int TYPE_HORIZONTAL_DOUBLE_ARROW = PointerIconType.HORIZONTAL_DOUBLE_ARROW;
130 
131     /** Type constant: vertical double arrow mainly for resizing. */
132     public static final int TYPE_VERTICAL_DOUBLE_ARROW = PointerIconType.VERTICAL_DOUBLE_ARROW;
133 
134     /** Type constant: diagonal double arrow -- top-right to bottom-left. */
135     public static final int TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW = 1016;
136 
137     /** Type constant: diagonal double arrow -- top-left to bottom-right. */
138     public static final int TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW = 1017;
139 
140     /** Type constant: zoom-in. */
141     public static final int TYPE_ZOOM_IN = PointerIconType.ZOOM_IN;
142 
143     /** Type constant: zoom-out. */
144     public static final int TYPE_ZOOM_OUT = PointerIconType.ZOOM_OUT;
145 
146     /** Type constant: grab. */
147     public static final int TYPE_GRAB = PointerIconType.GRAB;
148 
149     /** Type constant: grabbing. */
150     public static final int TYPE_GRABBING = PointerIconType.GRABBING;
151 
152     /** Type constant: handwriting. */
153     public static final int TYPE_HANDWRITING = PointerIconType.HANDWRITING;
154 
155     // OEM private types should be defined starting at this range to avoid
156     // conflicts with any system types that may be defined in the future.
157     private static final int TYPE_OEM_FIRST = 10000;
158 
159     /**
160      * The default pointer icon.
161      * @deprecated This is the same as using {@link #TYPE_ARROW}. Use {@link #TYPE_ARROW} to
162      *     explicitly show an arrow, or use a {@code null} {@link PointerIcon} with
163      *     {@link View#setPointerIcon(PointerIcon)} or
164      *     {@link View#onResolvePointerIcon(MotionEvent, int)} instead to show
165      *     the default pointer icon.
166      */
167     public static final int TYPE_DEFAULT = TYPE_ARROW;
168 
169     // A cache of the system icons used by the app, used to avoid creating a new PointerIcon object
170     // every time we need to resolve the icon (i.e. on each input event).
171     private static final SparseArray<PointerIcon> SYSTEM_ICONS = new SparseArray<>();
172 
173     /** @hide */
174     @IntDef(prefix = {"POINTER_ICON_VECTOR_STYLE_FILL_"}, value = {
175             POINTER_ICON_VECTOR_STYLE_FILL_BLACK,
176             POINTER_ICON_VECTOR_STYLE_FILL_GREEN,
177             POINTER_ICON_VECTOR_STYLE_FILL_RED,
178             POINTER_ICON_VECTOR_STYLE_FILL_PINK,
179             POINTER_ICON_VECTOR_STYLE_FILL_BLUE,
180             POINTER_ICON_VECTOR_STYLE_FILL_PURPLE
181     })
182     @Retention(RetentionPolicy.SOURCE)
183     public @interface PointerIconVectorStyleFill {}
184 
185     /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_BLACK = 0;
186     /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_GREEN = 1;
187     /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_RED = 2;
188     /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_PINK = 3;
189     /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_BLUE = 4;
190     /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_PURPLE = 5;
191 
192     // If adding a PointerIconVectorStyleFill, update END value for {@link SystemSettingsValidators}
193     /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_BEGIN =
194             POINTER_ICON_VECTOR_STYLE_FILL_BLACK;
195     /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_END =
196             POINTER_ICON_VECTOR_STYLE_FILL_PURPLE;
197 
198     /** @hide */
199     @IntDef(prefix = {"POINTER_ICON_VECTOR_STYLE_STROKE_"}, value = {
200             POINTER_ICON_VECTOR_STYLE_STROKE_WHITE,
201             POINTER_ICON_VECTOR_STYLE_STROKE_BLACK,
202             POINTER_ICON_VECTOR_STYLE_STROKE_NONE
203     })
204     @Retention(RetentionPolicy.SOURCE)
205     public @interface PointerIconVectorStyleStroke {}
206 
207     /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_STROKE_WHITE = 0;
208     /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_STROKE_BLACK = 1;
209     /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_STROKE_NONE = 2;
210 
211     // If adding PointerIconVectorStyleStroke, update END value for {@link SystemSettingsValidators}
212     /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_STROKE_BEGIN =
213             POINTER_ICON_VECTOR_STYLE_STROKE_WHITE;
214     /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_STROKE_END =
215             POINTER_ICON_VECTOR_STYLE_STROKE_NONE;
216 
217     /** @hide */ public static final float DEFAULT_POINTER_SCALE = 1f;
218     /** @hide */ public static final float LARGE_POINTER_SCALE = 2.5f;
219 
220     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
221     private final int mType;
222     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
223     private Bitmap mBitmap;
224     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
225     private float mHotSpotX;
226     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
227     private float mHotSpotY;
228     // The bitmaps for the additional frame of animated pointer icon. Note that the first frame
229     // will be stored in mBitmap.
230     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
231     private Bitmap mBitmapFrames[];
232     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
233     private int mDurationPerFrame;
234     @SuppressWarnings("unused")
235     private boolean mDrawNativeDropShadow;
236 
PointerIcon(int type)237     private PointerIcon(int type) {
238         mType = type;
239     }
240 
241     /**
242      * Gets a system pointer icon for the given type.
243      *
244      * @param context The context.
245      * @param type The pointer icon type.
246      * @return The pointer icon.
247      *
248      * @throws IllegalArgumentException if context is null.
249      */
getSystemIcon(@onNull Context context, int type)250     public static @NonNull PointerIcon getSystemIcon(@NonNull Context context, int type) {
251         if (context == null) {
252             // We no longer use the context to resolve the system icon resource here, because the
253             // system will use its own context to do the type-to-resource resolution and cache it
254             // for use across different apps. Therefore, apps cannot customize the resource of a
255             // system icon. To avoid changing the public API, we keep the context parameter
256             // requirement.
257             throw new IllegalArgumentException("context must not be null");
258         }
259         return getSystemIcon(type);
260     }
261 
getSystemIcon(int type)262     private static @NonNull PointerIcon getSystemIcon(int type) {
263         if (type == TYPE_CUSTOM) {
264             throw new IllegalArgumentException("cannot get system icon for TYPE_CUSTOM");
265         }
266         PointerIcon icon = SYSTEM_ICONS.get(type);
267         if (icon == null) {
268             icon = new PointerIcon(type);
269             SYSTEM_ICONS.put(type, icon);
270         }
271         return icon;
272     }
273 
274     /**
275      * Get a system icon with its resources loaded.
276      * This should only be used by the system for drawing icons to the screen.
277      * @hide
278      */
getLoadedSystemIcon(@onNull Context context, int type, boolean useLargeIcons, float pointerScale)279     public static @NonNull PointerIcon getLoadedSystemIcon(@NonNull Context context, int type,
280             boolean useLargeIcons, float pointerScale) {
281         if (type == TYPE_NOT_SPECIFIED) {
282             throw new IllegalStateException("Cannot load icon for type TYPE_NOT_SPECIFIED");
283         }
284 
285         if (type == TYPE_CUSTOM) {
286             throw new IllegalArgumentException("Custom icons must be loaded when they're created");
287         }
288 
289         int typeIndex = getSystemIconTypeIndex(type);
290         if (typeIndex < 0) {
291             typeIndex = getSystemIconTypeIndex(TYPE_DEFAULT);
292         }
293 
294         final int defStyle;
295         if (android.view.flags.Flags.enableVectorCursorA11ySettings()) {
296             defStyle = com.android.internal.R.style.VectorPointer;
297         } else {
298             // TODO(b/346358375): Remove useLargeIcons and the legacy pointer styles when
299             //  enableVectorCursorA11ySetting is rolled out.
300             if (useLargeIcons) {
301                 defStyle = com.android.internal.R.style.LargePointer;
302             } else if (android.view.flags.Flags.enableVectorCursors()) {
303                 defStyle = com.android.internal.R.style.VectorPointer;
304             } else {
305                 defStyle = com.android.internal.R.style.Pointer;
306             }
307         }
308         TypedArray a = context.obtainStyledAttributes(null,
309                 com.android.internal.R.styleable.Pointer,
310                 0, defStyle);
311         int resourceId = a.getResourceId(typeIndex, -1);
312         a.recycle();
313 
314         if (resourceId == -1) {
315             Log.w(TAG, "Missing theme resources for pointer icon type " + type);
316             return type == TYPE_DEFAULT
317                     ? getSystemIcon(TYPE_NULL)
318                     : getLoadedSystemIcon(context, TYPE_DEFAULT, useLargeIcons, pointerScale);
319         }
320 
321         final PointerIcon icon = new PointerIcon(type);
322         icon.loadResource(context.getResources(), resourceId, context.getTheme(), pointerScale);
323         return icon;
324     }
325 
isLoaded()326     private boolean isLoaded() {
327         return mBitmap != null && mHotSpotX >= 0 && mHotSpotX < mBitmap.getWidth() && mHotSpotY >= 0
328                 && mHotSpotY < mBitmap.getHeight();
329     }
330 
331     /**
332      * Creates a custom pointer icon from the given bitmap and hotspot information.
333      *
334      * @param bitmap The bitmap for the icon.
335      * @param hotSpotX The X offset of the pointer icon hotspot in the bitmap.
336      *        Must be within the [0, bitmap.getWidth()) range.
337      * @param hotSpotY The Y offset of the pointer icon hotspot in the bitmap.
338      *        Must be within the [0, bitmap.getHeight()) range.
339      * @return A pointer icon for this bitmap.
340      *
341      * @throws IllegalArgumentException if bitmap is null, or if the x/y hotspot
342      *         parameters are invalid.
343      */
create(@onNull Bitmap bitmap, float hotSpotX, float hotSpotY)344     public static @NonNull PointerIcon create(@NonNull Bitmap bitmap, float hotSpotX,
345             float hotSpotY) {
346         if (bitmap == null) {
347             throw new IllegalArgumentException("bitmap must not be null");
348         }
349         validateHotSpot(bitmap, hotSpotX, hotSpotY, false /* isScaled */);
350 
351         PointerIcon icon = new PointerIcon(TYPE_CUSTOM);
352         icon.mBitmap = bitmap;
353         icon.mHotSpotX = hotSpotX;
354         icon.mHotSpotY = hotSpotY;
355         return icon;
356     }
357 
358     /**
359      * Loads a custom pointer icon from an XML resource.
360      * <p>
361      * The XML resource should have the following form:
362      * <code>
363      * &lt;?xml version="1.0" encoding="utf-8"?&gt;
364      * &lt;pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
365      *   android:bitmap="@drawable/my_pointer_bitmap"
366      *   android:hotSpotX="24"
367      *   android:hotSpotY="24" /&gt;
368      * </code>
369      * </p>
370      *
371      * @param resources The resources object.
372      * @param resourceId The resource id.
373      * @return The pointer icon.
374      *
375      * @throws IllegalArgumentException if resources is null.
376      * @throws Resources.NotFoundException if the resource was not found or the drawable
377      * linked in the resource was not found.
378      */
load(@onNull Resources resources, @XmlRes int resourceId)379     public static @NonNull PointerIcon load(@NonNull Resources resources, @XmlRes int resourceId) {
380         if (resources == null) {
381             throw new IllegalArgumentException("resources must not be null");
382         }
383 
384         PointerIcon icon = new PointerIcon(TYPE_CUSTOM);
385         icon.loadResource(resources, resourceId, null, DEFAULT_POINTER_SCALE);
386         return icon;
387     }
388 
389     /** @hide */
getType()390     public int getType() {
391         return mType;
392     }
393 
394     public static final @NonNull Parcelable.Creator<PointerIcon> CREATOR =
395             new Parcelable.Creator<>() {
396                 @Override
397                 public PointerIcon createFromParcel(Parcel in) {
398                     final int type = in.readInt();
399                     if (type != TYPE_CUSTOM) {
400                         return getSystemIcon(type);
401                     }
402                     final PointerIcon icon =
403                             PointerIcon.create(
404                                     Bitmap.CREATOR.createFromParcel(in),
405                                     in.readFloat(),
406                                     in.readFloat());
407                     icon.mDrawNativeDropShadow = in.readBoolean();
408                     return icon;
409                 }
410 
411                 @Override
412                 public PointerIcon[] newArray(int size) {
413                     return new PointerIcon[size];
414                 }
415             };
416 
417     @Override
describeContents()418     public int describeContents() {
419         return 0;
420     }
421 
422     @Override
writeToParcel(Parcel out, int flags)423     public void writeToParcel(Parcel out, int flags) {
424         out.writeInt(mType);
425         if (mType != TYPE_CUSTOM) {
426             // When parceling a non-custom icon type, do not write the icon bitmap into the parcel
427             // because it can be re-loaded from resources after un-parceling.
428             return;
429         }
430 
431         if (!isLoaded()) {
432             throw new IllegalStateException("Custom icon should be loaded upon creation");
433         }
434         mBitmap.writeToParcel(out, flags);
435         out.writeFloat(mHotSpotX);
436         out.writeFloat(mHotSpotY);
437         out.writeBoolean(mDrawNativeDropShadow);
438     }
439 
440     @Override
equals(@ullable Object other)441     public boolean equals(@Nullable Object other) {
442         if (this == other) {
443             return true;
444         }
445 
446         if (other == null || !(other instanceof PointerIcon)) {
447             return false;
448         }
449 
450         PointerIcon otherIcon = (PointerIcon) other;
451         if (mType != otherIcon.mType) {
452             return false;
453         }
454 
455         if (mBitmap != otherIcon.mBitmap
456                 || mHotSpotX != otherIcon.mHotSpotX
457                 || mHotSpotY != otherIcon.mHotSpotY) {
458             return false;
459         }
460 
461         return true;
462     }
463 
464     /**
465      *  Get the Bitmap from the Drawable.
466      *
467      *  If the Bitmap needed to be scaled up to account for density, BitmapDrawable
468      *  handles this at draw time. But this class doesn't actually draw the Bitmap;
469      *  it is just a holder for native code to access its SkBitmap. So this needs to
470      *  get a version that is scaled to account for density.
471      */
getBitmapFromDrawable(BitmapDrawable bitmapDrawable)472     private Bitmap getBitmapFromDrawable(BitmapDrawable bitmapDrawable) {
473         Bitmap bitmap = bitmapDrawable.getBitmap();
474         final int scaledWidth  = bitmapDrawable.getIntrinsicWidth();
475         final int scaledHeight = bitmapDrawable.getIntrinsicHeight();
476         if (scaledWidth == bitmap.getWidth() && scaledHeight == bitmap.getHeight()) {
477             return bitmap;
478         }
479 
480         Rect src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
481         RectF dst = new RectF(0, 0, scaledWidth, scaledHeight);
482 
483         Bitmap scaled = Bitmap.createBitmap(scaledWidth, scaledHeight, bitmap.getConfig());
484         Canvas canvas = new Canvas(scaled);
485         Paint paint = new Paint();
486         paint.setFilterBitmap(true);
487         canvas.drawBitmap(bitmap, src, dst, paint);
488         return scaled;
489     }
490 
getBitmapDrawableFromVectorDrawable(Resources resources, VectorDrawable vectorDrawable, float pointerScale)491     private BitmapDrawable getBitmapDrawableFromVectorDrawable(Resources resources,
492             VectorDrawable vectorDrawable, float pointerScale) {
493         // Ensure we pass the display metrics into the Bitmap constructor so that it is initialized
494         // with the correct density.
495         Bitmap bitmap = Bitmap.createBitmap(resources.getDisplayMetrics(),
496                 (int) (vectorDrawable.getIntrinsicWidth() * pointerScale),
497                 (int) (vectorDrawable.getIntrinsicHeight() * pointerScale),
498                 Bitmap.Config.ARGB_8888, true /* hasAlpha */);
499         Canvas canvas = new Canvas(bitmap);
500         vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
501         vectorDrawable.draw(canvas);
502         return new BitmapDrawable(resources, bitmap);
503     }
504 
loadResource(@onNull Resources resources, @XmlRes int resourceId, @Nullable Resources.Theme theme, float pointerScale)505     private void loadResource(@NonNull Resources resources, @XmlRes int resourceId,
506             @Nullable Resources.Theme theme, float pointerScale) {
507         final XmlResourceParser parser = resources.getXml(resourceId);
508         final int bitmapRes;
509         final float hotSpotX;
510         final float hotSpotY;
511         try {
512             XmlUtils.beginDocument(parser, "pointer-icon");
513 
514             final TypedArray a = resources.obtainAttributes(
515                     parser, com.android.internal.R.styleable.PointerIcon);
516             bitmapRes = a.getResourceId(com.android.internal.R.styleable.PointerIcon_bitmap, 0);
517             // Cast the hotspot dimensions to int before scaling to match the scaling logic of
518             // the bitmap, whose intrinsic size is also an int before it is scaled.
519             final int unscaledHotSpotX =
520                     (int) a.getDimension(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0);
521             final int unscaledHotSpotY =
522                     (int) a.getDimension(com.android.internal.R.styleable.PointerIcon_hotSpotY, 0);
523             hotSpotX = unscaledHotSpotX * pointerScale;
524             hotSpotY = unscaledHotSpotY * pointerScale;
525             a.recycle();
526         } catch (Exception ex) {
527             throw new IllegalArgumentException("Exception parsing pointer icon resource.", ex);
528         } finally {
529             parser.close();
530         }
531 
532         if (bitmapRes == 0) {
533             throw new IllegalArgumentException("<pointer-icon> is missing bitmap attribute.");
534         }
535 
536         Drawable drawable = resources.getDrawable(bitmapRes, theme);
537         if (drawable instanceof AnimationDrawable) {
538             // Extract animation frame bitmaps.
539             final AnimationDrawable animationDrawable = (AnimationDrawable) drawable;
540             final int frames = animationDrawable.getNumberOfFrames();
541             drawable = animationDrawable.getFrame(0);
542             if (frames == 1) {
543                 Log.w(TAG, "Animation icon with single frame -- simply treating the first "
544                         + "frame as a normal bitmap icon.");
545             } else {
546                 // Assumes they have the exact duration.
547                 mDurationPerFrame = animationDrawable.getDuration(0);
548                 mBitmapFrames = new Bitmap[frames - 1];
549                 final boolean isVectorAnimation = drawable instanceof VectorDrawable;
550                 mDrawNativeDropShadow = isVectorAnimation;
551                 for (int i = 1; i < frames; ++i) {
552                     Drawable drawableFrame = animationDrawable.getFrame(i);
553                     if (!(drawableFrame instanceof BitmapDrawable)
554                             && !(drawableFrame instanceof VectorDrawable)) {
555                         throw new IllegalArgumentException("Frame of an animated pointer icon "
556                                 + "must refer to a bitmap drawable or vector drawable.");
557                     }
558                     if (isVectorAnimation != (drawableFrame instanceof VectorDrawable)) {
559                         throw new IllegalArgumentException("The drawable of the " + i + "-th frame "
560                                 + "is a different type from the others. All frames should be the "
561                                 + "same type.");
562                     }
563                     if (isVectorAnimation) {
564                         drawableFrame = getBitmapDrawableFromVectorDrawable(resources,
565                                 (VectorDrawable) drawableFrame, pointerScale);
566                     }
567                     mBitmapFrames[i - 1] = getBitmapFromDrawable((BitmapDrawable) drawableFrame);
568                 }
569             }
570         }
571         if (drawable instanceof VectorDrawable) {
572             mDrawNativeDropShadow = true;
573             drawable = getBitmapDrawableFromVectorDrawable(resources, (VectorDrawable) drawable,
574                     pointerScale);
575         }
576         if (!(drawable instanceof BitmapDrawable)) {
577             throw new IllegalArgumentException("<pointer-icon> bitmap attribute must "
578                     + "refer to a bitmap drawable.");
579         }
580 
581         BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
582         final Bitmap bitmap = getBitmapFromDrawable(bitmapDrawable);
583         // The bitmap and hotspot are loaded from the context, which means it is implicitly scaled
584         // to the current display density, so treat this as a scaled icon when verifying hotspot.
585         validateHotSpot(bitmap, hotSpotX, hotSpotY, true /* isScaled */);
586         // Set the properties now that we have successfully loaded the icon.
587         mBitmap = bitmap;
588         mHotSpotX = hotSpotX;
589         mHotSpotY = hotSpotY;
590         assert isLoaded();
591     }
592 
593     @Override
toString()594     public String toString() {
595         return "PointerIcon{type=" + typeToString(mType)
596                 + ", hotspotX=" + mHotSpotX + ", hotspotY=" + mHotSpotY + "}";
597     }
598 
validateHotSpot(Bitmap bitmap, float hotSpotX, float hotSpotY, boolean isScaled)599     private static void validateHotSpot(Bitmap bitmap, float hotSpotX, float hotSpotY,
600             boolean isScaled) {
601         // Be more lenient when checking the hotspot for scaled icons to account for the restriction
602         // that bitmaps must have an integer size.
603         if (hotSpotX < 0 || (isScaled ? (int) hotSpotX > bitmap.getWidth()
604                 : hotSpotX >= bitmap.getWidth())) {
605             throw new IllegalArgumentException("x hotspot lies outside of the bitmap area");
606         }
607         if (hotSpotY < 0 || (isScaled ? (int) hotSpotY > bitmap.getHeight()
608                 : hotSpotY >= bitmap.getHeight())) {
609             throw new IllegalArgumentException("y hotspot lies outside of the bitmap area");
610         }
611     }
612 
getSystemIconTypeIndex(int type)613     private static int getSystemIconTypeIndex(int type) {
614         switch (type) {
615             case TYPE_ARROW:
616                 return com.android.internal.R.styleable.Pointer_pointerIconArrow;
617             case TYPE_SPOT_HOVER:
618                 return com.android.internal.R.styleable.Pointer_pointerIconSpotHover;
619             case TYPE_SPOT_TOUCH:
620                 return com.android.internal.R.styleable.Pointer_pointerIconSpotTouch;
621             case TYPE_SPOT_ANCHOR:
622                 return com.android.internal.R.styleable.Pointer_pointerIconSpotAnchor;
623             case TYPE_HAND:
624                 return com.android.internal.R.styleable.Pointer_pointerIconHand;
625             case TYPE_CONTEXT_MENU:
626                 return com.android.internal.R.styleable.Pointer_pointerIconContextMenu;
627             case TYPE_HELP:
628                 return com.android.internal.R.styleable.Pointer_pointerIconHelp;
629             case TYPE_WAIT:
630                 return com.android.internal.R.styleable.Pointer_pointerIconWait;
631             case TYPE_CELL:
632                 return com.android.internal.R.styleable.Pointer_pointerIconCell;
633             case TYPE_CROSSHAIR:
634                 return com.android.internal.R.styleable.Pointer_pointerIconCrosshair;
635             case TYPE_TEXT:
636                 return com.android.internal.R.styleable.Pointer_pointerIconText;
637             case TYPE_VERTICAL_TEXT:
638                 return com.android.internal.R.styleable.Pointer_pointerIconVerticalText;
639             case TYPE_ALIAS:
640                 return com.android.internal.R.styleable.Pointer_pointerIconAlias;
641             case TYPE_COPY:
642                 return com.android.internal.R.styleable.Pointer_pointerIconCopy;
643             case TYPE_ALL_SCROLL:
644                 return com.android.internal.R.styleable.Pointer_pointerIconAllScroll;
645             case TYPE_NO_DROP:
646                 return com.android.internal.R.styleable.Pointer_pointerIconNodrop;
647             case TYPE_HORIZONTAL_DOUBLE_ARROW:
648                 return com.android.internal.R.styleable.Pointer_pointerIconHorizontalDoubleArrow;
649             case TYPE_VERTICAL_DOUBLE_ARROW:
650                 return com.android.internal.R.styleable.Pointer_pointerIconVerticalDoubleArrow;
651             case TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW:
652                 return com.android.internal.R.styleable.
653                         Pointer_pointerIconTopRightDiagonalDoubleArrow;
654             case TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW:
655                 return com.android.internal.R.styleable.
656                         Pointer_pointerIconTopLeftDiagonalDoubleArrow;
657             case TYPE_ZOOM_IN:
658                 return com.android.internal.R.styleable.Pointer_pointerIconZoomIn;
659             case TYPE_ZOOM_OUT:
660                 return com.android.internal.R.styleable.Pointer_pointerIconZoomOut;
661             case TYPE_GRAB:
662                 return com.android.internal.R.styleable.Pointer_pointerIconGrab;
663             case TYPE_GRABBING:
664                 return com.android.internal.R.styleable.Pointer_pointerIconGrabbing;
665             case TYPE_HANDWRITING:
666                 return com.android.internal.R.styleable.Pointer_pointerIconHandwriting;
667             default:
668                 return -1;
669         }
670     }
671 
672     /**
673      * Convert type constant to string.
674      * @hide
675      */
typeToString(int type)676     public static String typeToString(int type) {
677         switch (type) {
678             case TYPE_CUSTOM: return "CUSTOM";
679             case TYPE_NULL: return "NULL";
680             case TYPE_NOT_SPECIFIED: return "NOT_SPECIFIED";
681             case TYPE_ARROW: return "ARROW";
682             case TYPE_SPOT_HOVER: return "SPOT_HOVER";
683             case TYPE_SPOT_TOUCH: return "SPOT_TOUCH";
684             case TYPE_SPOT_ANCHOR: return "SPOT_ANCHOR";
685             case TYPE_CONTEXT_MENU: return "CONTEXT_MENU";
686             case TYPE_HAND: return "HAND";
687             case TYPE_HELP: return "HELP";
688             case TYPE_WAIT: return "WAIT";
689             case TYPE_CELL: return "CELL";
690             case TYPE_CROSSHAIR: return "CROSSHAIR";
691             case TYPE_TEXT: return "TEXT";
692             case TYPE_VERTICAL_TEXT: return "VERTICAL_TEXT";
693             case TYPE_ALIAS: return "ALIAS";
694             case TYPE_COPY: return "COPY";
695             case TYPE_NO_DROP: return "NO_DROP";
696             case TYPE_ALL_SCROLL: return "ALL_SCROLL";
697             case TYPE_HORIZONTAL_DOUBLE_ARROW: return "HORIZONTAL_DOUBLE_ARROW";
698             case TYPE_VERTICAL_DOUBLE_ARROW: return "VERTICAL_DOUBLE_ARROW";
699             case TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW: return "TOP_RIGHT_DIAGONAL_DOUBLE_ARROW";
700             case TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW: return "TOP_LEFT_DIAGONAL_DOUBLE_ARROW";
701             case TYPE_ZOOM_IN: return "ZOOM_IN";
702             case TYPE_ZOOM_OUT: return "ZOOM_OUT";
703             case TYPE_GRAB: return "GRAB";
704             case TYPE_GRABBING: return "GRABBING";
705             case TYPE_HANDWRITING: return "HANDWRITING";
706             default: return Integer.toString(type);
707         }
708     }
709 
710     /**
711      * Convert fill style constant to resource ID.
712      *
713      * @hide
714      */
vectorFillStyleToResource(@ointerIconVectorStyleFill int fillStyle)715     public static int vectorFillStyleToResource(@PointerIconVectorStyleFill int fillStyle) {
716         return switch (fillStyle) {
717             case POINTER_ICON_VECTOR_STYLE_FILL_BLACK ->
718                     com.android.internal.R.style.PointerIconVectorStyleFillBlack;
719             case POINTER_ICON_VECTOR_STYLE_FILL_GREEN ->
720                     com.android.internal.R.style.PointerIconVectorStyleFillGreen;
721             case POINTER_ICON_VECTOR_STYLE_FILL_RED ->
722                     com.android.internal.R.style.PointerIconVectorStyleFillRed;
723             case POINTER_ICON_VECTOR_STYLE_FILL_PINK ->
724                     com.android.internal.R.style.PointerIconVectorStyleFillPink;
725             case POINTER_ICON_VECTOR_STYLE_FILL_BLUE ->
726                     com.android.internal.R.style.PointerIconVectorStyleFillBlue;
727             case POINTER_ICON_VECTOR_STYLE_FILL_PURPLE ->
728                     com.android.internal.R.style.PointerIconVectorStyleFillPurple;
729             default -> com.android.internal.R.style.PointerIconVectorStyleFillBlack;
730         };
731     }
732 
733     /**
734      * Convert stroke style constant to resource ID.
735      *
736      * @hide
737      */
vectorStrokeStyleToResource(@ointerIconVectorStyleStroke int strokeStyle)738     public static int vectorStrokeStyleToResource(@PointerIconVectorStyleStroke int strokeStyle) {
739         return switch (strokeStyle) {
740             case POINTER_ICON_VECTOR_STYLE_STROKE_BLACK ->
741                     com.android.internal.R.style.PointerIconVectorStyleStrokeBlack;
742             case POINTER_ICON_VECTOR_STYLE_STROKE_WHITE ->
743                     com.android.internal.R.style.PointerIconVectorStyleStrokeWhite;
744             case POINTER_ICON_VECTOR_STYLE_STROKE_NONE ->
745                     com.android.internal.R.style.PointerIconVectorStyleStrokeNone;
746             default -> com.android.internal.R.style.PointerIconVectorStyleStrokeWhite;
747         };
748     }
749 
750     /**
751      * Sets whether drop shadow will draw in the native code.
752      *
753      * @hide
754      */
755     @TestApi
756     @FlaggedApi(Flags.FLAG_ENABLE_VECTOR_CURSORS)
757     public void setDrawNativeDropShadow(boolean drawNativeDropShadow) {
758         mDrawNativeDropShadow = drawNativeDropShadow;
759     }
760 
761     /**
762      * Gets the PointerIcon's bitmap.
763      *
764      * @hide
765      */
766     @VisibleForTesting
767     public Bitmap getBitmap() {
768         return mBitmap;
769     }
770 }
771