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