• 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_YELLOW,
178             POINTER_ICON_VECTOR_STYLE_FILL_PINK,
179             POINTER_ICON_VECTOR_STYLE_FILL_BLUE
180     })
181     @Retention(RetentionPolicy.SOURCE)
182     public @interface PointerIconVectorStyleFill {}
183 
184     /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_BLACK = 0;
185     /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_GREEN = 1;
186     /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_YELLOW = 2;
187     /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_PINK = 3;
188     /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_BLUE = 4;
189 
190     // If adding a PointerIconVectorStyleFill, update END value for {@link SystemSettingsValidators}
191     /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_BEGIN =
192             POINTER_ICON_VECTOR_STYLE_FILL_BLACK;
193     /** @hide */ public static final int POINTER_ICON_VECTOR_STYLE_FILL_END =
194             POINTER_ICON_VECTOR_STYLE_FILL_BLUE;
195 
196     /** @hide */ public static final float DEFAULT_POINTER_SCALE = 1f;
197     /** @hide */ public static final float LARGE_POINTER_SCALE = 2.5f;
198 
199     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
200     private final int mType;
201     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
202     private Bitmap mBitmap;
203     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
204     private float mHotSpotX;
205     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
206     private float mHotSpotY;
207     // The bitmaps for the additional frame of animated pointer icon. Note that the first frame
208     // will be stored in mBitmap.
209     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
210     private Bitmap mBitmapFrames[];
211     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
212     private int mDurationPerFrame;
213     @SuppressWarnings("unused")
214     private boolean mDrawNativeDropShadow;
215 
PointerIcon(int type)216     private PointerIcon(int type) {
217         mType = type;
218     }
219 
220     /**
221      * Gets a system pointer icon for the given type.
222      *
223      * @param context The context.
224      * @param type The pointer icon type.
225      * @return The pointer icon.
226      *
227      * @throws IllegalArgumentException if context is null.
228      */
getSystemIcon(@onNull Context context, int type)229     public static @NonNull PointerIcon getSystemIcon(@NonNull Context context, int type) {
230         if (context == null) {
231             // We no longer use the context to resolve the system icon resource here, because the
232             // system will use its own context to do the type-to-resource resolution and cache it
233             // for use across different apps. Therefore, apps cannot customize the resource of a
234             // system icon. To avoid changing the public API, we keep the context parameter
235             // requirement.
236             throw new IllegalArgumentException("context must not be null");
237         }
238         return getSystemIcon(type);
239     }
240 
getSystemIcon(int type)241     private static @NonNull PointerIcon getSystemIcon(int type) {
242         if (type == TYPE_CUSTOM) {
243             throw new IllegalArgumentException("cannot get system icon for TYPE_CUSTOM");
244         }
245         PointerIcon icon = SYSTEM_ICONS.get(type);
246         if (icon == null) {
247             icon = new PointerIcon(type);
248             SYSTEM_ICONS.put(type, icon);
249         }
250         return icon;
251     }
252 
253     /**
254      * Get a system icon with its resources loaded.
255      * This should only be used by the system for drawing icons to the screen.
256      * @hide
257      */
getLoadedSystemIcon(@onNull Context context, int type, boolean useLargeIcons, float pointerScale)258     public static @NonNull PointerIcon getLoadedSystemIcon(@NonNull Context context, int type,
259             boolean useLargeIcons, float pointerScale) {
260         if (type == TYPE_NOT_SPECIFIED) {
261             throw new IllegalStateException("Cannot load icon for type TYPE_NOT_SPECIFIED");
262         }
263 
264         if (type == TYPE_CUSTOM) {
265             throw new IllegalArgumentException("Custom icons must be loaded when they're created");
266         }
267 
268         int typeIndex = getSystemIconTypeIndex(type);
269         if (typeIndex < 0) {
270             typeIndex = getSystemIconTypeIndex(TYPE_DEFAULT);
271         }
272 
273         final int defStyle;
274         if (android.view.flags.Flags.enableVectorCursorA11ySettings()) {
275             defStyle = com.android.internal.R.style.VectorPointer;
276         } else {
277             // TODO(b/346358375): Remove useLargeIcons and the legacy pointer styles when
278             //  enableVectorCursorA11ySetting is rolled out.
279             if (useLargeIcons) {
280                 defStyle = com.android.internal.R.style.LargePointer;
281             } else if (android.view.flags.Flags.enableVectorCursors()) {
282                 defStyle = com.android.internal.R.style.VectorPointer;
283             } else {
284                 defStyle = com.android.internal.R.style.Pointer;
285             }
286         }
287         TypedArray a = context.obtainStyledAttributes(null,
288                 com.android.internal.R.styleable.Pointer,
289                 0, defStyle);
290         int resourceId = a.getResourceId(typeIndex, -1);
291         a.recycle();
292 
293         if (resourceId == -1) {
294             Log.w(TAG, "Missing theme resources for pointer icon type " + type);
295             return type == TYPE_DEFAULT
296                     ? getSystemIcon(TYPE_NULL)
297                     : getLoadedSystemIcon(context, TYPE_DEFAULT, useLargeIcons, pointerScale);
298         }
299 
300         final PointerIcon icon = new PointerIcon(type);
301         icon.loadResource(context.getResources(), resourceId, context.getTheme(), pointerScale);
302         return icon;
303     }
304 
isLoaded()305     private boolean isLoaded() {
306         return mBitmap != null && mHotSpotX >= 0 && mHotSpotX < mBitmap.getWidth() && mHotSpotY >= 0
307                 && mHotSpotY < mBitmap.getHeight();
308     }
309 
310     /**
311      * Creates a custom pointer icon from the given bitmap and hotspot information.
312      *
313      * @param bitmap The bitmap for the icon.
314      * @param hotSpotX The X offset of the pointer icon hotspot in the bitmap.
315      *        Must be within the [0, bitmap.getWidth()) range.
316      * @param hotSpotY The Y offset of the pointer icon hotspot in the bitmap.
317      *        Must be within the [0, bitmap.getHeight()) range.
318      * @return A pointer icon for this bitmap.
319      *
320      * @throws IllegalArgumentException if bitmap is null, or if the x/y hotspot
321      *         parameters are invalid.
322      */
create(@onNull Bitmap bitmap, float hotSpotX, float hotSpotY)323     public static @NonNull PointerIcon create(@NonNull Bitmap bitmap, float hotSpotX,
324             float hotSpotY) {
325         if (bitmap == null) {
326             throw new IllegalArgumentException("bitmap must not be null");
327         }
328         validateHotSpot(bitmap, hotSpotX, hotSpotY, false /* isScaled */);
329 
330         PointerIcon icon = new PointerIcon(TYPE_CUSTOM);
331         icon.mBitmap = bitmap;
332         icon.mHotSpotX = hotSpotX;
333         icon.mHotSpotY = hotSpotY;
334         return icon;
335     }
336 
337     /**
338      * Loads a custom pointer icon from an XML resource.
339      * <p>
340      * The XML resource should have the following form:
341      * <code>
342      * &lt;?xml version="1.0" encoding="utf-8"?&gt;
343      * &lt;pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
344      *   android:bitmap="@drawable/my_pointer_bitmap"
345      *   android:hotSpotX="24"
346      *   android:hotSpotY="24" /&gt;
347      * </code>
348      * </p>
349      *
350      * @param resources The resources object.
351      * @param resourceId The resource id.
352      * @return The pointer icon.
353      *
354      * @throws IllegalArgumentException if resources is null.
355      * @throws Resources.NotFoundException if the resource was not found or the drawable
356      * linked in the resource was not found.
357      */
load(@onNull Resources resources, @XmlRes int resourceId)358     public static @NonNull PointerIcon load(@NonNull Resources resources, @XmlRes int resourceId) {
359         if (resources == null) {
360             throw new IllegalArgumentException("resources must not be null");
361         }
362 
363         PointerIcon icon = new PointerIcon(TYPE_CUSTOM);
364         icon.loadResource(resources, resourceId, null, DEFAULT_POINTER_SCALE);
365         return icon;
366     }
367 
368     /** @hide */
getType()369     public int getType() {
370         return mType;
371     }
372 
373     public static final @NonNull Parcelable.Creator<PointerIcon> CREATOR =
374             new Parcelable.Creator<>() {
375                 @Override
376                 public PointerIcon createFromParcel(Parcel in) {
377                     final int type = in.readInt();
378                     if (type != TYPE_CUSTOM) {
379                         return getSystemIcon(type);
380                     }
381                     final PointerIcon icon =
382                             PointerIcon.create(
383                                     Bitmap.CREATOR.createFromParcel(in),
384                                     in.readFloat(),
385                                     in.readFloat());
386                     icon.mDrawNativeDropShadow = in.readBoolean();
387                     return icon;
388                 }
389 
390                 @Override
391                 public PointerIcon[] newArray(int size) {
392                     return new PointerIcon[size];
393                 }
394             };
395 
396     @Override
describeContents()397     public int describeContents() {
398         return 0;
399     }
400 
401     @Override
writeToParcel(Parcel out, int flags)402     public void writeToParcel(Parcel out, int flags) {
403         out.writeInt(mType);
404         if (mType != TYPE_CUSTOM) {
405             // When parceling a non-custom icon type, do not write the icon bitmap into the parcel
406             // because it can be re-loaded from resources after un-parceling.
407             return;
408         }
409 
410         if (!isLoaded()) {
411             throw new IllegalStateException("Custom icon should be loaded upon creation");
412         }
413         mBitmap.writeToParcel(out, flags);
414         out.writeFloat(mHotSpotX);
415         out.writeFloat(mHotSpotY);
416         out.writeBoolean(mDrawNativeDropShadow);
417     }
418 
419     @Override
equals(@ullable Object other)420     public boolean equals(@Nullable Object other) {
421         if (this == other) {
422             return true;
423         }
424 
425         if (other == null || !(other instanceof PointerIcon)) {
426             return false;
427         }
428 
429         PointerIcon otherIcon = (PointerIcon) other;
430         if (mType != otherIcon.mType) {
431             return false;
432         }
433 
434         if (mBitmap != otherIcon.mBitmap
435                 || mHotSpotX != otherIcon.mHotSpotX
436                 || mHotSpotY != otherIcon.mHotSpotY) {
437             return false;
438         }
439 
440         return true;
441     }
442 
443     /**
444      *  Get the Bitmap from the Drawable.
445      *
446      *  If the Bitmap needed to be scaled up to account for density, BitmapDrawable
447      *  handles this at draw time. But this class doesn't actually draw the Bitmap;
448      *  it is just a holder for native code to access its SkBitmap. So this needs to
449      *  get a version that is scaled to account for density.
450      */
getBitmapFromDrawable(BitmapDrawable bitmapDrawable)451     private Bitmap getBitmapFromDrawable(BitmapDrawable bitmapDrawable) {
452         Bitmap bitmap = bitmapDrawable.getBitmap();
453         final int scaledWidth  = bitmapDrawable.getIntrinsicWidth();
454         final int scaledHeight = bitmapDrawable.getIntrinsicHeight();
455         if (scaledWidth == bitmap.getWidth() && scaledHeight == bitmap.getHeight()) {
456             return bitmap;
457         }
458 
459         Rect src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
460         RectF dst = new RectF(0, 0, scaledWidth, scaledHeight);
461 
462         Bitmap scaled = Bitmap.createBitmap(scaledWidth, scaledHeight, bitmap.getConfig());
463         Canvas canvas = new Canvas(scaled);
464         Paint paint = new Paint();
465         paint.setFilterBitmap(true);
466         canvas.drawBitmap(bitmap, src, dst, paint);
467         return scaled;
468     }
469 
getBitmapDrawableFromVectorDrawable(Resources resources, VectorDrawable vectorDrawable, float pointerScale)470     private BitmapDrawable getBitmapDrawableFromVectorDrawable(Resources resources,
471             VectorDrawable vectorDrawable, float pointerScale) {
472         // Ensure we pass the display metrics into the Bitmap constructor so that it is initialized
473         // with the correct density.
474         Bitmap bitmap = Bitmap.createBitmap(resources.getDisplayMetrics(),
475                 (int) (vectorDrawable.getIntrinsicWidth() * pointerScale),
476                 (int) (vectorDrawable.getIntrinsicHeight() * pointerScale),
477                 Bitmap.Config.ARGB_8888, true /* hasAlpha */);
478         Canvas canvas = new Canvas(bitmap);
479         vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
480         vectorDrawable.draw(canvas);
481         return new BitmapDrawable(resources, bitmap);
482     }
483 
loadResource(@onNull Resources resources, @XmlRes int resourceId, @Nullable Resources.Theme theme, float pointerScale)484     private void loadResource(@NonNull Resources resources, @XmlRes int resourceId,
485             @Nullable Resources.Theme theme, float pointerScale) {
486         final XmlResourceParser parser = resources.getXml(resourceId);
487         final int bitmapRes;
488         final float hotSpotX;
489         final float hotSpotY;
490         try {
491             XmlUtils.beginDocument(parser, "pointer-icon");
492 
493             final TypedArray a = resources.obtainAttributes(
494                     parser, com.android.internal.R.styleable.PointerIcon);
495             bitmapRes = a.getResourceId(com.android.internal.R.styleable.PointerIcon_bitmap, 0);
496             hotSpotX = a.getDimension(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0)
497                     * pointerScale;
498             hotSpotY = a.getDimension(com.android.internal.R.styleable.PointerIcon_hotSpotY, 0)
499                     * pointerScale;
500             a.recycle();
501         } catch (Exception ex) {
502             throw new IllegalArgumentException("Exception parsing pointer icon resource.", ex);
503         } finally {
504             parser.close();
505         }
506 
507         if (bitmapRes == 0) {
508             throw new IllegalArgumentException("<pointer-icon> is missing bitmap attribute.");
509         }
510 
511         Drawable drawable = resources.getDrawable(bitmapRes, theme);
512         if (drawable instanceof AnimationDrawable) {
513             // Extract animation frame bitmaps.
514             final AnimationDrawable animationDrawable = (AnimationDrawable) drawable;
515             final int frames = animationDrawable.getNumberOfFrames();
516             drawable = animationDrawable.getFrame(0);
517             if (frames == 1) {
518                 Log.w(TAG, "Animation icon with single frame -- simply treating the first "
519                         + "frame as a normal bitmap icon.");
520             } else {
521                 // Assumes they have the exact duration.
522                 mDurationPerFrame = animationDrawable.getDuration(0);
523                 mBitmapFrames = new Bitmap[frames - 1];
524                 final int width = drawable.getIntrinsicWidth();
525                 final int height = drawable.getIntrinsicHeight();
526                 final boolean isVectorAnimation = drawable instanceof VectorDrawable;
527                 mDrawNativeDropShadow = isVectorAnimation;
528                 for (int i = 1; i < frames; ++i) {
529                     Drawable drawableFrame = animationDrawable.getFrame(i);
530                     if (!(drawableFrame instanceof BitmapDrawable)
531                             && !(drawableFrame instanceof VectorDrawable)) {
532                         throw new IllegalArgumentException("Frame of an animated pointer icon "
533                                 + "must refer to a bitmap drawable or vector drawable.");
534                     }
535                     if (isVectorAnimation != (drawableFrame instanceof VectorDrawable)) {
536                         throw new IllegalArgumentException("The drawable of the " + i + "-th frame "
537                                 + "is a different type from the others. All frames should be the "
538                                 + "same type.");
539                     }
540                     if (drawableFrame.getIntrinsicWidth() != width ||
541                         drawableFrame.getIntrinsicHeight() != height) {
542                         throw new IllegalArgumentException("The bitmap size of " + i + "-th frame "
543                                 + "is different. All frames should have the exact same size and "
544                                 + "share the same hotspot.");
545                     }
546                     if (isVectorAnimation) {
547                         drawableFrame = getBitmapDrawableFromVectorDrawable(resources,
548                                 (VectorDrawable) drawableFrame, pointerScale);
549                     }
550                     mBitmapFrames[i - 1] = getBitmapFromDrawable((BitmapDrawable) drawableFrame);
551                 }
552             }
553         }
554         if (drawable instanceof VectorDrawable) {
555             mDrawNativeDropShadow = true;
556             drawable = getBitmapDrawableFromVectorDrawable(resources, (VectorDrawable) drawable,
557                     pointerScale);
558         }
559         if (!(drawable instanceof BitmapDrawable)) {
560             throw new IllegalArgumentException("<pointer-icon> bitmap attribute must "
561                     + "refer to a bitmap drawable.");
562         }
563 
564         BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
565         final Bitmap bitmap = getBitmapFromDrawable(bitmapDrawable);
566         // The bitmap and hotspot are loaded from the context, which means it is implicitly scaled
567         // to the current display density, so treat this as a scaled icon when verifying hotspot.
568         validateHotSpot(bitmap, hotSpotX, hotSpotY, true /* isScaled */);
569         // Set the properties now that we have successfully loaded the icon.
570         mBitmap = bitmap;
571         mHotSpotX = hotSpotX;
572         mHotSpotY = hotSpotY;
573         assert isLoaded();
574     }
575 
576     @Override
toString()577     public String toString() {
578         return "PointerIcon{type=" + typeToString(mType)
579                 + ", hotspotX=" + mHotSpotX + ", hotspotY=" + mHotSpotY + "}";
580     }
581 
validateHotSpot(Bitmap bitmap, float hotSpotX, float hotSpotY, boolean isScaled)582     private static void validateHotSpot(Bitmap bitmap, float hotSpotX, float hotSpotY,
583             boolean isScaled) {
584         // Be more lenient when checking the hotspot for scaled icons to account for the restriction
585         // that bitmaps must have an integer size.
586         if (hotSpotX < 0 || (isScaled ? (int) hotSpotX > bitmap.getWidth()
587                 : hotSpotX >= bitmap.getWidth())) {
588             throw new IllegalArgumentException("x hotspot lies outside of the bitmap area");
589         }
590         if (hotSpotY < 0 || (isScaled ? (int) hotSpotY > bitmap.getHeight()
591                 : hotSpotY >= bitmap.getHeight())) {
592             throw new IllegalArgumentException("y hotspot lies outside of the bitmap area");
593         }
594     }
595 
getSystemIconTypeIndex(int type)596     private static int getSystemIconTypeIndex(int type) {
597         switch (type) {
598             case TYPE_ARROW:
599                 return com.android.internal.R.styleable.Pointer_pointerIconArrow;
600             case TYPE_SPOT_HOVER:
601                 return com.android.internal.R.styleable.Pointer_pointerIconSpotHover;
602             case TYPE_SPOT_TOUCH:
603                 return com.android.internal.R.styleable.Pointer_pointerIconSpotTouch;
604             case TYPE_SPOT_ANCHOR:
605                 return com.android.internal.R.styleable.Pointer_pointerIconSpotAnchor;
606             case TYPE_HAND:
607                 return com.android.internal.R.styleable.Pointer_pointerIconHand;
608             case TYPE_CONTEXT_MENU:
609                 return com.android.internal.R.styleable.Pointer_pointerIconContextMenu;
610             case TYPE_HELP:
611                 return com.android.internal.R.styleable.Pointer_pointerIconHelp;
612             case TYPE_WAIT:
613                 return com.android.internal.R.styleable.Pointer_pointerIconWait;
614             case TYPE_CELL:
615                 return com.android.internal.R.styleable.Pointer_pointerIconCell;
616             case TYPE_CROSSHAIR:
617                 return com.android.internal.R.styleable.Pointer_pointerIconCrosshair;
618             case TYPE_TEXT:
619                 return com.android.internal.R.styleable.Pointer_pointerIconText;
620             case TYPE_VERTICAL_TEXT:
621                 return com.android.internal.R.styleable.Pointer_pointerIconVerticalText;
622             case TYPE_ALIAS:
623                 return com.android.internal.R.styleable.Pointer_pointerIconAlias;
624             case TYPE_COPY:
625                 return com.android.internal.R.styleable.Pointer_pointerIconCopy;
626             case TYPE_ALL_SCROLL:
627                 return com.android.internal.R.styleable.Pointer_pointerIconAllScroll;
628             case TYPE_NO_DROP:
629                 return com.android.internal.R.styleable.Pointer_pointerIconNodrop;
630             case TYPE_HORIZONTAL_DOUBLE_ARROW:
631                 return com.android.internal.R.styleable.Pointer_pointerIconHorizontalDoubleArrow;
632             case TYPE_VERTICAL_DOUBLE_ARROW:
633                 return com.android.internal.R.styleable.Pointer_pointerIconVerticalDoubleArrow;
634             case TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW:
635                 return com.android.internal.R.styleable.
636                         Pointer_pointerIconTopRightDiagonalDoubleArrow;
637             case TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW:
638                 return com.android.internal.R.styleable.
639                         Pointer_pointerIconTopLeftDiagonalDoubleArrow;
640             case TYPE_ZOOM_IN:
641                 return com.android.internal.R.styleable.Pointer_pointerIconZoomIn;
642             case TYPE_ZOOM_OUT:
643                 return com.android.internal.R.styleable.Pointer_pointerIconZoomOut;
644             case TYPE_GRAB:
645                 return com.android.internal.R.styleable.Pointer_pointerIconGrab;
646             case TYPE_GRABBING:
647                 return com.android.internal.R.styleable.Pointer_pointerIconGrabbing;
648             case TYPE_HANDWRITING:
649                 return com.android.internal.R.styleable.Pointer_pointerIconHandwriting;
650             default:
651                 return -1;
652         }
653     }
654 
655     /**
656      * Convert type constant to string.
657      * @hide
658      */
typeToString(int type)659     public static String typeToString(int type) {
660         switch (type) {
661             case TYPE_CUSTOM: return "CUSTOM";
662             case TYPE_NULL: return "NULL";
663             case TYPE_NOT_SPECIFIED: return "NOT_SPECIFIED";
664             case TYPE_ARROW: return "ARROW";
665             case TYPE_SPOT_HOVER: return "SPOT_HOVER";
666             case TYPE_SPOT_TOUCH: return "SPOT_TOUCH";
667             case TYPE_SPOT_ANCHOR: return "SPOT_ANCHOR";
668             case TYPE_CONTEXT_MENU: return "CONTEXT_MENU";
669             case TYPE_HAND: return "HAND";
670             case TYPE_HELP: return "HELP";
671             case TYPE_WAIT: return "WAIT";
672             case TYPE_CELL: return "CELL";
673             case TYPE_CROSSHAIR: return "CROSSHAIR";
674             case TYPE_TEXT: return "TEXT";
675             case TYPE_VERTICAL_TEXT: return "VERTICAL_TEXT";
676             case TYPE_ALIAS: return "ALIAS";
677             case TYPE_COPY: return "COPY";
678             case TYPE_NO_DROP: return "NO_DROP";
679             case TYPE_ALL_SCROLL: return "ALL_SCROLL";
680             case TYPE_HORIZONTAL_DOUBLE_ARROW: return "HORIZONTAL_DOUBLE_ARROW";
681             case TYPE_VERTICAL_DOUBLE_ARROW: return "VERTICAL_DOUBLE_ARROW";
682             case TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW: return "TOP_RIGHT_DIAGONAL_DOUBLE_ARROW";
683             case TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW: return "TOP_LEFT_DIAGONAL_DOUBLE_ARROW";
684             case TYPE_ZOOM_IN: return "ZOOM_IN";
685             case TYPE_ZOOM_OUT: return "ZOOM_OUT";
686             case TYPE_GRAB: return "GRAB";
687             case TYPE_GRABBING: return "GRABBING";
688             case TYPE_HANDWRITING: return "HANDWRITING";
689             default: return Integer.toString(type);
690         }
691     }
692 
693     /**
694      * Convert fill style constant to resource ID.
695      *
696      * @hide
697      */
vectorFillStyleToResource(@ointerIconVectorStyleFill int fillStyle)698     public static int vectorFillStyleToResource(@PointerIconVectorStyleFill int fillStyle) {
699         return switch (fillStyle) {
700             case POINTER_ICON_VECTOR_STYLE_FILL_BLACK ->
701                     com.android.internal.R.style.PointerIconVectorStyleFillBlack;
702             case POINTER_ICON_VECTOR_STYLE_FILL_GREEN ->
703                     com.android.internal.R.style.PointerIconVectorStyleFillGreen;
704             case POINTER_ICON_VECTOR_STYLE_FILL_YELLOW ->
705                     com.android.internal.R.style.PointerIconVectorStyleFillYellow;
706             case POINTER_ICON_VECTOR_STYLE_FILL_PINK ->
707                     com.android.internal.R.style.PointerIconVectorStyleFillPink;
708             case POINTER_ICON_VECTOR_STYLE_FILL_BLUE ->
709                     com.android.internal.R.style.PointerIconVectorStyleFillBlue;
710             default -> com.android.internal.R.style.PointerIconVectorStyleFillBlack;
711         };
712     }
713 
714     /**
715      * Sets whether drop shadow will draw in the native code.
716      *
717      * @hide
718      */
719     @TestApi
720     @FlaggedApi(Flags.FLAG_ENABLE_VECTOR_CURSORS)
setDrawNativeDropShadow(boolean drawNativeDropShadow)721     public void setDrawNativeDropShadow(boolean drawNativeDropShadow) {
722         mDrawNativeDropShadow = drawNativeDropShadow;
723     }
724 
725     /**
726      * Gets the PointerIcon's bitmap.
727      *
728      * @hide
729      */
730     @VisibleForTesting
getBitmap()731     public Bitmap getBitmap() {
732         return mBitmap;
733     }
734 }
735