Icon.java revision 24f11b1871f9def5d802d107897658c4b2b66ab5
1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.graphics.drawable; 18 19import android.annotation.ColorInt; 20import android.annotation.DrawableRes; 21import android.content.res.ColorStateList; 22import android.content.ContentResolver; 23import android.content.Context; 24import android.content.pm.PackageManager; 25import android.content.res.Resources; 26import android.graphics.Bitmap; 27import android.graphics.BitmapFactory; 28import android.graphics.PorterDuff; 29import android.net.Uri; 30import android.os.AsyncTask; 31import android.os.Handler; 32import android.os.Message; 33import android.os.Parcel; 34import android.os.Parcelable; 35import android.text.TextUtils; 36import android.util.Log; 37 38import java.io.DataInputStream; 39import java.io.DataOutputStream; 40import java.io.File; 41import java.io.FileInputStream; 42import java.io.FileNotFoundException; 43import java.io.IOException; 44import java.io.InputStream; 45import java.io.OutputStream; 46 47/** 48 * An umbrella container for several serializable graphics representations, including Bitmaps, 49 * compressed bitmap images (e.g. JPG or PNG), and drawable resources (including vectors). 50 * 51 * <a href="https://developer.android.com/training/displaying-bitmaps/index.html">Much ink</a> 52 * has been spilled on the best way to load images, and many clients may have different needs when 53 * it comes to threading and fetching. This class is therefore focused on encapsulation rather than 54 * behavior. 55 */ 56 57public final class Icon implements Parcelable { 58 private static final String TAG = "Icon"; 59 60 /** @hide */ 61 public static final int TYPE_BITMAP = 1; 62 /** @hide */ 63 public static final int TYPE_RESOURCE = 2; 64 /** @hide */ 65 public static final int TYPE_DATA = 3; 66 /** @hide */ 67 public static final int TYPE_URI = 4; 68 69 private static final int VERSION_STREAM_SERIALIZER = 1; 70 71 private final int mType; 72 73 private ColorStateList mTintList; 74 static final PorterDuff.Mode DEFAULT_TINT_MODE = Drawable.DEFAULT_TINT_MODE; // SRC_IN 75 private PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE; 76 77 // To avoid adding unnecessary overhead, we have a few basic objects that get repurposed 78 // based on the value of mType. 79 80 // TYPE_BITMAP: Bitmap 81 // TYPE_RESOURCE: Resources 82 // TYPE_DATA: DataBytes 83 private Object mObj1; 84 85 // TYPE_RESOURCE: package name 86 // TYPE_URI: uri string 87 private String mString1; 88 89 // TYPE_RESOURCE: resId 90 // TYPE_DATA: data length 91 private int mInt1; 92 93 // TYPE_DATA: data offset 94 private int mInt2; 95 96 /** 97 * @return The type of image data held in this Icon. One of 98 * {@link #TYPE_BITMAP}, 99 * {@link #TYPE_RESOURCE}, 100 * {@link #TYPE_DATA}, or 101 * {@link #TYPE_URI}. 102 * @hide 103 */ 104 public int getType() { 105 return mType; 106 } 107 108 /** 109 * @return The {@link android.graphics.Bitmap} held by this {@link #TYPE_BITMAP} Icon. 110 * @hide 111 */ 112 public Bitmap getBitmap() { 113 if (mType != TYPE_BITMAP) { 114 throw new IllegalStateException("called getBitmap() on " + this); 115 } 116 return (Bitmap) mObj1; 117 } 118 119 /** 120 * @return The length of the compressed bitmap byte array held by this {@link #TYPE_DATA} Icon. 121 * @hide 122 */ 123 public int getDataLength() { 124 if (mType != TYPE_DATA) { 125 throw new IllegalStateException("called getDataLength() on " + this); 126 } 127 synchronized (this) { 128 return mInt1; 129 } 130 } 131 132 /** 133 * @return The offset into the byte array held by this {@link #TYPE_DATA} Icon at which 134 * valid compressed bitmap data is found. 135 * @hide 136 */ 137 public int getDataOffset() { 138 if (mType != TYPE_DATA) { 139 throw new IllegalStateException("called getDataOffset() on " + this); 140 } 141 synchronized (this) { 142 return mInt2; 143 } 144 } 145 146 /** 147 * @return The byte array held by this {@link #TYPE_DATA} Icon ctonaining compressed 148 * bitmap data. 149 * @hide 150 */ 151 public byte[] getDataBytes() { 152 if (mType != TYPE_DATA) { 153 throw new IllegalStateException("called getDataBytes() on " + this); 154 } 155 synchronized (this) { 156 return (byte[]) mObj1; 157 } 158 } 159 160 /** 161 * @return The {@link android.content.res.Resources} for this {@link #TYPE_RESOURCE} Icon. 162 * @hide 163 */ 164 public Resources getResources() { 165 if (mType != TYPE_RESOURCE) { 166 throw new IllegalStateException("called getResources() on " + this); 167 } 168 return (Resources) mObj1; 169 } 170 171 /** 172 * @return The package containing resources for this {@link #TYPE_RESOURCE} Icon. 173 * @hide 174 */ 175 public String getResPackage() { 176 if (mType != TYPE_RESOURCE) { 177 throw new IllegalStateException("called getResPackage() on " + this); 178 } 179 return mString1; 180 } 181 182 /** 183 * @return The resource ID for this {@link #TYPE_RESOURCE} Icon. 184 * @hide 185 */ 186 public int getResId() { 187 if (mType != TYPE_RESOURCE) { 188 throw new IllegalStateException("called getResId() on " + this); 189 } 190 return mInt1; 191 } 192 193 /** 194 * @return The URI (as a String) for this {@link #TYPE_URI} Icon. 195 * @hide 196 */ 197 public String getUriString() { 198 if (mType != TYPE_URI) { 199 throw new IllegalStateException("called getUriString() on " + this); 200 } 201 return mString1; 202 } 203 204 /** 205 * @return The {@link android.net.Uri} for this {@link #TYPE_URI} Icon. 206 * @hide 207 */ 208 public Uri getUri() { 209 return Uri.parse(getUriString()); 210 } 211 212 private static final String typeToString(int x) { 213 switch (x) { 214 case TYPE_BITMAP: return "BITMAP"; 215 case TYPE_DATA: return "DATA"; 216 case TYPE_RESOURCE: return "RESOURCE"; 217 case TYPE_URI: return "URI"; 218 default: return "UNKNOWN"; 219 } 220 } 221 222 /** 223 * Invokes {@link #loadDrawable(Context)} on the given {@link android.os.Handler Handler} 224 * and then sends <code>andThen</code> to the same Handler when finished. 225 * 226 * @param context {@link android.content.Context Context} in which to load the drawable; see 227 * {@link #loadDrawable(Context)} 228 * @param andThen {@link android.os.Message} to send to its target once the drawable 229 * is available. The {@link android.os.Message#obj obj} 230 * property is populated with the Drawable. 231 */ 232 public void loadDrawableAsync(Context context, Message andThen) { 233 if (andThen.getTarget() == null) { 234 throw new IllegalArgumentException("callback message must have a target handler"); 235 } 236 new LoadDrawableTask(context, andThen).runAsync(); 237 } 238 239 /** 240 * Invokes {@link #loadDrawable(Context)} on a background thread 241 * and then runs <code>andThen</code> on the UI thread when finished. 242 * 243 * @param context {@link Context Context} in which to load the drawable; see 244 * {@link #loadDrawable(Context)} 245 * @param listener a callback to run on the provided 246 * @param handler {@link Handler} on which to run <code>andThen</code>. 247 */ 248 public void loadDrawableAsync(Context context, final OnDrawableLoadedListener listener, 249 Handler handler) { 250 new LoadDrawableTask(context, handler, listener).runAsync(); 251 } 252 253 /** 254 * Returns a Drawable that can be used to draw the image inside this Icon, constructing it 255 * if necessary. Depending on the type of image, this may not be something you want to do on 256 * the UI thread, so consider using 257 * {@link #loadDrawableAsync(Context, Message) loadDrawableAsync} instead. 258 * 259 * @param context {@link android.content.Context Context} in which to load the drawable; used 260 * to access {@link android.content.res.Resources Resources}, for example. 261 * @return A fresh instance of a drawable for this image, yours to keep. 262 */ 263 public Drawable loadDrawable(Context context) { 264 final Drawable result = loadDrawableInner(context); 265 if (result != null && (mTintList != null || mTintMode != DEFAULT_TINT_MODE)) { 266 result.mutate(); 267 result.setTintList(mTintList); 268 result.setTintMode(mTintMode); 269 } 270 return result; 271 } 272 273 /** 274 * Do the heavy lifting of loading the drawable, but stop short of applying any tint. 275 */ 276 private Drawable loadDrawableInner(Context context) { 277 switch (mType) { 278 case TYPE_BITMAP: 279 return new BitmapDrawable(context.getResources(), getBitmap()); 280 case TYPE_RESOURCE: 281 if (getResources() == null) { 282 // figure out where to load resources from 283 String resPackage = getResPackage(); 284 if (TextUtils.isEmpty(resPackage)) { 285 // if none is specified, try the given context 286 resPackage = context.getPackageName(); 287 } 288 if ("android".equals(resPackage)) { 289 mObj1 = Resources.getSystem(); 290 } else { 291 final PackageManager pm = context.getPackageManager(); 292 try { 293 mObj1 = pm.getResourcesForApplication(resPackage); 294 } catch (PackageManager.NameNotFoundException e) { 295 Log.e(TAG, String.format("Unable to find pkg=%s for icon %s", 296 resPackage, this), e); 297 break; 298 } 299 } 300 } 301 try { 302 return getResources().getDrawable(getResId(), context.getTheme()); 303 } catch (RuntimeException e) { 304 Log.e(TAG, String.format("Unable to load resource 0x%08x from pkg=%s", 305 getResId(), 306 getResPackage()), 307 e); 308 } 309 break; 310 case TYPE_DATA: 311 return new BitmapDrawable(context.getResources(), 312 BitmapFactory.decodeByteArray(getDataBytes(), getDataOffset(), getDataLength()) 313 ); 314 case TYPE_URI: 315 final Uri uri = getUri(); 316 final String scheme = uri.getScheme(); 317 InputStream is = null; 318 if (ContentResolver.SCHEME_CONTENT.equals(scheme) 319 || ContentResolver.SCHEME_FILE.equals(scheme)) { 320 try { 321 is = context.getContentResolver().openInputStream(uri); 322 } catch (Exception e) { 323 Log.w(TAG, "Unable to load image from URI: " + uri, e); 324 } 325 } else { 326 try { 327 is = new FileInputStream(new File(mString1)); 328 } catch (FileNotFoundException e) { 329 Log.w(TAG, "Unable to load image from path: " + uri, e); 330 } 331 } 332 if (is != null) { 333 return new BitmapDrawable(context.getResources(), 334 BitmapFactory.decodeStream(is)); 335 } 336 break; 337 } 338 return null; 339 } 340 341 /** 342 * Load the requested resources under the given userId, if the system allows it, 343 * before actually loading the drawable. 344 * 345 * @hide 346 */ 347 public Drawable loadDrawableAsUser(Context context, int userId) { 348 if (mType == TYPE_RESOURCE) { 349 String resPackage = getResPackage(); 350 if (TextUtils.isEmpty(resPackage)) { 351 resPackage = context.getPackageName(); 352 } 353 if (getResources() == null && !(getResPackage().equals("android"))) { 354 final PackageManager pm = context.getPackageManager(); 355 try { 356 // assign getResources() as the correct user 357 mObj1 = pm.getResourcesForApplicationAsUser(resPackage, userId); 358 } catch (PackageManager.NameNotFoundException e) { 359 Log.e(TAG, String.format("Unable to find pkg=%s user=%d", 360 getResPackage(), 361 userId), 362 e); 363 } 364 } 365 } 366 return loadDrawable(context); 367 } 368 369 /** 370 * Writes a serialized version of an Icon to the specified stream. 371 * 372 * @param stream The stream on which to serialize the Icon. 373 * @hide 374 */ 375 public void writeToStream(OutputStream stream) throws IOException { 376 DataOutputStream dataStream = new DataOutputStream(stream); 377 378 dataStream.writeInt(VERSION_STREAM_SERIALIZER); 379 dataStream.writeByte(mType); 380 381 switch (mType) { 382 case TYPE_BITMAP: 383 getBitmap().compress(Bitmap.CompressFormat.PNG, 100, dataStream); 384 break; 385 case TYPE_DATA: 386 dataStream.writeInt(getDataLength()); 387 dataStream.write(getDataBytes(), getDataOffset(), getDataLength()); 388 break; 389 case TYPE_RESOURCE: 390 dataStream.writeUTF(getResPackage()); 391 dataStream.writeInt(getResId()); 392 break; 393 case TYPE_URI: 394 dataStream.writeUTF(getUriString()); 395 break; 396 } 397 } 398 399 private Icon(int mType) { 400 this.mType = mType; 401 } 402 403 /** 404 * Create an Icon from the specified stream. 405 * 406 * @param stream The input stream from which to reconstruct the Icon. 407 * @hide 408 */ 409 public static Icon createFromStream(InputStream stream) throws IOException { 410 DataInputStream inputStream = new DataInputStream(stream); 411 412 final int version = inputStream.readInt(); 413 if (version >= VERSION_STREAM_SERIALIZER) { 414 final int type = inputStream.readByte(); 415 switch (type) { 416 case TYPE_BITMAP: 417 return createWithBitmap(BitmapFactory.decodeStream(inputStream)); 418 case TYPE_DATA: 419 final int length = inputStream.readInt(); 420 final byte[] data = new byte[length]; 421 inputStream.read(data, 0 /* offset */, length); 422 return createWithData(data, 0 /* offset */, length); 423 case TYPE_RESOURCE: 424 final String packageName = inputStream.readUTF(); 425 final int resId = inputStream.readInt(); 426 return createWithResource(packageName, resId); 427 case TYPE_URI: 428 final String uriOrPath = inputStream.readUTF(); 429 return createWithContentUri(uriOrPath); 430 } 431 } 432 return null; 433 } 434 435 /** 436 * Create an Icon pointing to a drawable resource. 437 * @param context The context for the application whose resources should be used to resolve the 438 * given resource ID. 439 * @param resId ID of the drawable resource 440 */ 441 public static Icon createWithResource(Context context, @DrawableRes int resId) { 442 if (context == null) { 443 throw new IllegalArgumentException("Context must not be null."); 444 } 445 final Icon rep = new Icon(TYPE_RESOURCE); 446 rep.mInt1 = resId; 447 rep.mString1 = context.getPackageName(); 448 return rep; 449 } 450 451 /** 452 * Version of createWithResource that takes Resources. Do not use. 453 * @hide 454 */ 455 public static Icon createWithResource(Resources res, @DrawableRes int resId) { 456 if (res == null) { 457 throw new IllegalArgumentException("Resource must not be null."); 458 } 459 final Icon rep = new Icon(TYPE_RESOURCE); 460 rep.mInt1 = resId; 461 rep.mString1 = res.getResourcePackageName(resId); 462 return rep; 463 } 464 465 /** 466 * Create an Icon pointing to a drawable resource. 467 * @param resPackage Name of the package containing the resource in question 468 * @param resId ID of the drawable resource 469 */ 470 public static Icon createWithResource(String resPackage, @DrawableRes int resId) { 471 if (resPackage == null) { 472 throw new IllegalArgumentException("Resource package name must not be null."); 473 } 474 final Icon rep = new Icon(TYPE_RESOURCE); 475 rep.mInt1 = resId; 476 rep.mString1 = resPackage; 477 return rep; 478 } 479 480 /** 481 * Create an Icon pointing to a bitmap in memory. 482 * @param bits A valid {@link android.graphics.Bitmap} object 483 */ 484 public static Icon createWithBitmap(Bitmap bits) { 485 if (bits == null) { 486 throw new IllegalArgumentException("Bitmap must not be null."); 487 } 488 final Icon rep = new Icon(TYPE_BITMAP); 489 rep.mObj1 = bits; 490 return rep; 491 } 492 493 /** 494 * Create an Icon pointing to a compressed bitmap stored in a byte array. 495 * @param data Byte array storing compressed bitmap data of a type that 496 * {@link android.graphics.BitmapFactory} 497 * can decode (see {@link android.graphics.Bitmap.CompressFormat}). 498 * @param offset Offset into <code>data</code> at which the bitmap data starts 499 * @param length Length of the bitmap data 500 */ 501 public static Icon createWithData(byte[] data, int offset, int length) { 502 if (data == null) { 503 throw new IllegalArgumentException("Data must not be null."); 504 } 505 final Icon rep = new Icon(TYPE_DATA); 506 rep.mObj1 = data; 507 rep.mInt1 = length; 508 rep.mInt2 = offset; 509 return rep; 510 } 511 512 /** 513 * Create an Icon pointing to an image file specified by URI. 514 * 515 * @param uri A uri referring to local content:// or file:// image data. 516 */ 517 public static Icon createWithContentUri(String uri) { 518 if (uri == null) { 519 throw new IllegalArgumentException("Uri must not be null."); 520 } 521 final Icon rep = new Icon(TYPE_URI); 522 rep.mString1 = uri; 523 return rep; 524 } 525 526 /** 527 * Create an Icon pointing to an image file specified by URI. 528 * 529 * @param uri A uri referring to local content:// or file:// image data. 530 */ 531 public static Icon createWithContentUri(Uri uri) { 532 if (uri == null) { 533 throw new IllegalArgumentException("Uri must not be null."); 534 } 535 final Icon rep = new Icon(TYPE_URI); 536 rep.mString1 = uri.toString(); 537 return rep; 538 } 539 540 /** 541 * Store a color to use whenever this Icon is drawn. 542 * 543 * @param tint a color, as in {@link Drawable#setTint(int)} 544 * @return this same object, for use in chained construction 545 */ 546 public Icon setTint(@ColorInt int tint) { 547 return setTintList(ColorStateList.valueOf(tint)); 548 } 549 550 /** 551 * Store a color to use whenever this Icon is drawn. 552 * 553 * @param tintList as in {@link Drawable#setTintList(ColorStateList)}, null to remove tint 554 * @return this same object, for use in chained construction 555 */ 556 public Icon setTintList(ColorStateList tintList) { 557 mTintList = tintList; 558 return this; 559 } 560 561 /** 562 * Store a blending mode to use whenever this Icon is drawn. 563 * 564 * @param mode a blending mode, as in {@link Drawable#setTintMode(PorterDuff.Mode)}, may be null 565 * @return this same object, for use in chained construction 566 */ 567 public Icon setTintMode(PorterDuff.Mode mode) { 568 mTintMode = mode; 569 return this; 570 } 571 572 /** 573 * Create an Icon pointing to an image file specified by path. 574 * 575 * @param path A path to a file that contains compressed bitmap data of 576 * a type that {@link android.graphics.BitmapFactory} can decode. 577 */ 578 public static Icon createWithFilePath(String path) { 579 if (path == null) { 580 throw new IllegalArgumentException("Path must not be null."); 581 } 582 final Icon rep = new Icon(TYPE_URI); 583 rep.mString1 = path; 584 return rep; 585 } 586 587 @Override 588 public String toString() { 589 final StringBuilder sb = new StringBuilder("Icon(typ=").append(typeToString(mType)); 590 switch (mType) { 591 case TYPE_BITMAP: 592 sb.append(" size=") 593 .append(getBitmap().getWidth()) 594 .append("x") 595 .append(getBitmap().getHeight()); 596 break; 597 case TYPE_RESOURCE: 598 sb.append(" pkg=") 599 .append(getResPackage()) 600 .append(" id=") 601 .append(String.format("0x%08x", getResId())); 602 break; 603 case TYPE_DATA: 604 sb.append(" len=").append(getDataLength()); 605 if (getDataOffset() != 0) { 606 sb.append(" off=").append(getDataOffset()); 607 } 608 break; 609 case TYPE_URI: 610 sb.append(" uri=").append(getUriString()); 611 break; 612 } 613 if (mTintList != null) { 614 sb.append(" tint="); 615 String sep = ""; 616 for (int c : mTintList.getColors()) { 617 sb.append(String.format("%s0x%08x", sep, c)); 618 sep = "|"; 619 } 620 } 621 if (mTintMode != DEFAULT_TINT_MODE) sb.append(" mode=").append(mTintMode); 622 sb.append(")"); 623 return sb.toString(); 624 } 625 626 /** 627 * Parcelable interface 628 */ 629 public int describeContents() { 630 return (mType == TYPE_BITMAP || mType == TYPE_DATA) 631 ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0; 632 } 633 634 // ===== Parcelable interface ====== 635 636 private Icon(Parcel in) { 637 this(in.readInt()); 638 switch (mType) { 639 case TYPE_BITMAP: 640 final Bitmap bits = Bitmap.CREATOR.createFromParcel(in); 641 mObj1 = bits; 642 break; 643 case TYPE_RESOURCE: 644 final String pkg = in.readString(); 645 final int resId = in.readInt(); 646 mString1 = pkg; 647 mInt1 = resId; 648 break; 649 case TYPE_DATA: 650 final int len = in.readInt(); 651 final byte[] a = in.readBlob(); 652 if (len != a.length) { 653 throw new RuntimeException("internal unparceling error: blob length (" 654 + a.length + ") != expected length (" + len + ")"); 655 } 656 mInt1 = len; 657 mObj1 = a; 658 break; 659 case TYPE_URI: 660 final String uri = in.readString(); 661 mString1 = uri; 662 break; 663 default: 664 throw new RuntimeException("invalid " 665 + this.getClass().getSimpleName() + " type in parcel: " + mType); 666 } 667 if (in.readInt() == 1) { 668 mTintList = ColorStateList.CREATOR.createFromParcel(in); 669 } 670 mTintMode = PorterDuff.intToMode(in.readInt()); 671 } 672 673 @Override 674 public void writeToParcel(Parcel dest, int flags) { 675 dest.writeInt(mType); 676 switch (mType) { 677 case TYPE_BITMAP: 678 final Bitmap bits = getBitmap(); 679 getBitmap().writeToParcel(dest, flags); 680 break; 681 case TYPE_RESOURCE: 682 dest.writeString(getResPackage()); 683 dest.writeInt(getResId()); 684 break; 685 case TYPE_DATA: 686 dest.writeInt(getDataLength()); 687 dest.writeBlob(getDataBytes(), getDataOffset(), getDataLength()); 688 break; 689 case TYPE_URI: 690 dest.writeString(getUriString()); 691 break; 692 } 693 if (mTintList == null) { 694 dest.writeInt(0); 695 } else { 696 dest.writeInt(1); 697 mTintList.writeToParcel(dest, flags); 698 } 699 dest.writeInt(PorterDuff.modeToInt(mTintMode)); 700 } 701 702 public static final Parcelable.Creator<Icon> CREATOR 703 = new Parcelable.Creator<Icon>() { 704 public Icon createFromParcel(Parcel in) { 705 return new Icon(in); 706 } 707 708 public Icon[] newArray(int size) { 709 return new Icon[size]; 710 } 711 }; 712 713 /** 714 * Implement this interface to receive a callback when 715 * {@link #loadDrawableAsync(Context, OnDrawableLoadedListener, Handler) loadDrawableAsync} 716 * is finished and your Drawable is ready. 717 */ 718 public interface OnDrawableLoadedListener { 719 void onDrawableLoaded(Drawable d); 720 } 721 722 /** 723 * Wrapper around loadDrawable that does its work on a pooled thread and then 724 * fires back the given (targeted) Message. 725 */ 726 private class LoadDrawableTask implements Runnable { 727 final Context mContext; 728 final Message mMessage; 729 730 public LoadDrawableTask(Context context, final Handler handler, 731 final OnDrawableLoadedListener listener) { 732 mContext = context; 733 mMessage = Message.obtain(handler, new Runnable() { 734 @Override 735 public void run() { 736 listener.onDrawableLoaded((Drawable) mMessage.obj); 737 } 738 }); 739 } 740 741 public LoadDrawableTask(Context context, Message message) { 742 mContext = context; 743 mMessage = message; 744 } 745 746 @Override 747 public void run() { 748 mMessage.obj = loadDrawable(mContext); 749 mMessage.sendToTarget(); 750 } 751 752 public void runAsync() { 753 AsyncTask.THREAD_POOL_EXECUTOR.execute(this); 754 } 755 } 756} 757