1 /*
2  * Copyright (C) 2006 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.text.style;
18 
19 import android.annotation.DrawableRes;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.compat.annotation.UnsupportedAppUsage;
23 import android.content.Context;
24 import android.graphics.Bitmap;
25 import android.graphics.BitmapFactory;
26 import android.graphics.drawable.BitmapDrawable;
27 import android.graphics.drawable.Drawable;
28 import android.net.Uri;
29 import android.util.Log;
30 
31 import java.io.InputStream;
32 
33 /**
34  * Span that replaces the text it's attached to with a {@link Drawable} that can be aligned with
35  * the bottom or with the baseline of the surrounding text. The drawable can be constructed from
36  * varied sources:
37  * <ul>
38  * <li>{@link Bitmap} - see {@link #ImageSpan(Context, Bitmap)} and
39  * {@link #ImageSpan(Context, Bitmap, int)}
40  * </li>
41  * <li>{@link Drawable} - see {@link #ImageSpan(Drawable, int)}</li>
42  * <li>resource id - see {@link #ImageSpan(Context, int, int)}</li>
43  * <li>{@link Uri} - see {@link #ImageSpan(Context, Uri, int)}</li>
44  * </ul>
45  * The default value for the vertical alignment is {@link DynamicDrawableSpan#ALIGN_BOTTOM}
46  * <p>
47  * For example, an <code>ImagedSpan</code> can be used like this:
48  * <pre>
49  * SpannableString string = new SpannableString("Bottom: span.\nBaseline: span.");
50  * // using the default alignment: ALIGN_BOTTOM
51  * string.setSpan(new ImageSpan(this, R.mipmap.ic_launcher), 7, 8, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
52  * string.setSpan(new ImageSpan(this, R.mipmap.ic_launcher, DynamicDrawableSpan.ALIGN_BASELINE),
53  * 22, 23, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
54  * </pre>
55  * <img src="{@docRoot}reference/android/images/text/style/imagespan.png" />
56  * <figcaption>Text with <code>ImageSpan</code>s aligned bottom and baseline.</figcaption>
57  */
58 public class ImageSpan extends DynamicDrawableSpan {
59 
60     @Nullable
61     @UnsupportedAppUsage
62     private Drawable mDrawable;
63     @Nullable
64     private Uri mContentUri;
65     @DrawableRes
66     private int mResourceId;
67     @Nullable
68     private Context mContext;
69     @Nullable
70     private String mSource;
71 
72     /**
73      * @deprecated Use {@link #ImageSpan(Context, Bitmap)} instead.
74      */
75     @Deprecated
ImageSpan(@onNull Bitmap b)76     public ImageSpan(@NonNull Bitmap b) {
77         this(null, b, ALIGN_BOTTOM);
78     }
79 
80     /**
81      * @deprecated Use {@link #ImageSpan(Context, Bitmap, int)} instead.
82      */
83     @Deprecated
ImageSpan(@onNull Bitmap b, int verticalAlignment)84     public ImageSpan(@NonNull Bitmap b, int verticalAlignment) {
85         this(null, b, verticalAlignment);
86     }
87 
88     /**
89      * Constructs an {@link ImageSpan} from a {@link Context} and a {@link Bitmap} with the default
90      * alignment {@link DynamicDrawableSpan#ALIGN_BOTTOM}
91      *
92      * @param context context used to create a drawable from {@param bitmap} based on the display
93      *                metrics of the resources
94      * @param bitmap  bitmap to be rendered
95      */
ImageSpan(@onNull Context context, @NonNull Bitmap bitmap)96     public ImageSpan(@NonNull Context context, @NonNull Bitmap bitmap) {
97         this(context, bitmap, ALIGN_BOTTOM);
98     }
99 
100     /**
101      * Constructs an {@link ImageSpan} from a {@link Context}, a {@link Bitmap} and a vertical
102      * alignment.
103      *
104      * @param context           context used to create a drawable from {@param bitmap} based on
105      *                          the display metrics of the resources
106      * @param bitmap            bitmap to be rendered
107      * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or
108      *                          {@link DynamicDrawableSpan#ALIGN_BASELINE}
109      */
ImageSpan(@onNull Context context, @NonNull Bitmap bitmap, int verticalAlignment)110     public ImageSpan(@NonNull Context context, @NonNull Bitmap bitmap, int verticalAlignment) {
111         super(verticalAlignment);
112         mContext = context;
113         mDrawable = context != null
114                 ? new BitmapDrawable(context.getResources(), bitmap)
115                 : new BitmapDrawable(bitmap);
116         int width = mDrawable.getIntrinsicWidth();
117         int height = mDrawable.getIntrinsicHeight();
118         mDrawable.setBounds(0, 0, width > 0 ? width : 0, height > 0 ? height : 0);
119     }
120 
121     /**
122      * Constructs an {@link ImageSpan} from a drawable with the default
123      * alignment {@link DynamicDrawableSpan#ALIGN_BOTTOM}.
124      *
125      * @param drawable drawable to be rendered
126      */
ImageSpan(@onNull Drawable drawable)127     public ImageSpan(@NonNull Drawable drawable) {
128         this(drawable, ALIGN_BOTTOM);
129     }
130 
131     /**
132      * Constructs an {@link ImageSpan} from a drawable and a vertical alignment.
133      *
134      * @param drawable          drawable to be rendered
135      * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or
136      *                          {@link DynamicDrawableSpan#ALIGN_BASELINE}
137      */
ImageSpan(@onNull Drawable drawable, int verticalAlignment)138     public ImageSpan(@NonNull Drawable drawable, int verticalAlignment) {
139         super(verticalAlignment);
140         mDrawable = drawable;
141     }
142 
143     /**
144      * Constructs an {@link ImageSpan} from a drawable and a source with the default
145      * alignment {@link DynamicDrawableSpan#ALIGN_BOTTOM}
146      *
147      * @param drawable drawable to be rendered
148      * @param source   drawable's Uri source
149      */
ImageSpan(@onNull Drawable drawable, @NonNull String source)150     public ImageSpan(@NonNull Drawable drawable, @NonNull String source) {
151         this(drawable, source, ALIGN_BOTTOM);
152     }
153 
154     /**
155      * Constructs an {@link ImageSpan} from a drawable, a source and a vertical alignment.
156      *
157      * @param drawable          drawable to be rendered
158      * @param source            drawable's uri source
159      * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or
160      *                          {@link DynamicDrawableSpan#ALIGN_BASELINE}
161      */
ImageSpan(@onNull Drawable drawable, @NonNull String source, int verticalAlignment)162     public ImageSpan(@NonNull Drawable drawable, @NonNull String source, int verticalAlignment) {
163         super(verticalAlignment);
164         mDrawable = drawable;
165         mSource = source;
166     }
167 
168     /**
169      * Constructs an {@link ImageSpan} from a {@link Context} and a {@link Uri} with the default
170      * alignment {@link DynamicDrawableSpan#ALIGN_BOTTOM}. The Uri source can be retrieved via
171      * {@link #getSource()}
172      *
173      * @param context context used to create a drawable from {@param bitmap} based on the display
174      *                metrics of the resources
175      * @param uri     {@link Uri} used to construct the drawable that will be rendered
176      */
ImageSpan(@onNull Context context, @NonNull Uri uri)177     public ImageSpan(@NonNull Context context, @NonNull Uri uri) {
178         this(context, uri, ALIGN_BOTTOM);
179     }
180 
181     /**
182      * Constructs an {@link ImageSpan} from a {@link Context}, a {@link Uri} and a vertical
183      * alignment. The Uri source can be retrieved via {@link #getSource()}
184      *
185      * @param context           context used to create a drawable from {@param bitmap} based on
186      *                          the display
187      *                          metrics of the resources
188      * @param uri               {@link Uri} used to construct the drawable that will be rendered.
189      * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or
190      *                          {@link DynamicDrawableSpan#ALIGN_BASELINE}
191      */
ImageSpan(@onNull Context context, @NonNull Uri uri, int verticalAlignment)192     public ImageSpan(@NonNull Context context, @NonNull Uri uri, int verticalAlignment) {
193         super(verticalAlignment);
194         mContext = context;
195         mContentUri = uri;
196         mSource = uri.toString();
197     }
198 
199     /**
200      * Constructs an {@link ImageSpan} from a {@link Context} and a resource id with the default
201      * alignment {@link DynamicDrawableSpan#ALIGN_BOTTOM}
202      *
203      * @param context    context used to retrieve the drawable from resources
204      * @param resourceId drawable resource id based on which the drawable is retrieved
205      */
ImageSpan(@onNull Context context, @DrawableRes int resourceId)206     public ImageSpan(@NonNull Context context, @DrawableRes int resourceId) {
207         this(context, resourceId, ALIGN_BOTTOM);
208     }
209 
210     /**
211      * Constructs an {@link ImageSpan} from a {@link Context}, a resource id and a vertical
212      * alignment.
213      *
214      * @param context           context used to retrieve the drawable from resources
215      * @param resourceId        drawable resource id based on which the drawable is retrieved.
216      * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or
217      *                          {@link DynamicDrawableSpan#ALIGN_BASELINE}
218      */
ImageSpan(@onNull Context context, @DrawableRes int resourceId, int verticalAlignment)219     public ImageSpan(@NonNull Context context, @DrawableRes int resourceId,
220             int verticalAlignment) {
221         super(verticalAlignment);
222         mContext = context;
223         mResourceId = resourceId;
224     }
225 
226     @Override
getDrawable()227     public Drawable getDrawable() {
228         Drawable drawable = null;
229 
230         if (mDrawable != null) {
231             drawable = mDrawable;
232         } else if (mContentUri != null) {
233             Bitmap bitmap = null;
234             try {
235                 InputStream is = mContext.getContentResolver().openInputStream(
236                         mContentUri);
237                 bitmap = BitmapFactory.decodeStream(is);
238                 drawable = new BitmapDrawable(mContext.getResources(), bitmap);
239                 drawable.setBounds(0, 0, drawable.getIntrinsicWidth(),
240                         drawable.getIntrinsicHeight());
241                 is.close();
242             } catch (Exception e) {
243                 Log.e("ImageSpan", "Failed to loaded content " + mContentUri, e);
244             }
245         } else {
246             try {
247                 drawable = mContext.getDrawable(mResourceId);
248                 drawable.setBounds(0, 0, drawable.getIntrinsicWidth(),
249                         drawable.getIntrinsicHeight());
250             } catch (Exception e) {
251                 Log.e("ImageSpan", "Unable to find resource: " + mResourceId);
252             }
253         }
254 
255         return drawable;
256     }
257 
258     /**
259      * Returns the source string that was saved during construction.
260      *
261      * @return the source string that was saved during construction
262      * @see #ImageSpan(Drawable, String)
263      * @see #ImageSpan(Context, Uri)
264      */
265     @Nullable
getSource()266     public String getSource() {
267         return mSource;
268     }
269 }
270