1 /* 2 * Copyright (C) 2017 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.graphics; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.ContentResolver; 22 import android.content.res.AssetManager.AssetInputStream; 23 import android.content.res.Resources; 24 import android.graphics.drawable.AnimatedImageDrawable; 25 import android.graphics.drawable.Drawable; 26 import android.graphics.drawable.BitmapDrawable; 27 import android.net.Uri; 28 import android.util.DisplayMetrics; 29 import android.util.Size; 30 import android.util.TypedValue; 31 32 import java.nio.ByteBuffer; 33 import java.io.File; 34 import java.io.IOException; 35 import java.io.InputStream; 36 import java.lang.ArrayIndexOutOfBoundsException; 37 import java.lang.AutoCloseable; 38 import java.lang.NullPointerException; 39 import java.lang.annotation.Retention; 40 import static java.lang.annotation.RetentionPolicy.SOURCE; 41 42 /** 43 * Class for decoding images as {@link Bitmap}s or {@link Drawable}s. 44 */ 45 public final class ImageDecoder implements AutoCloseable { 46 47 /** 48 * Source of the encoded image data. 49 */ 50 public static abstract class Source { Source()51 private Source() {} 52 53 /* @hide */ 54 @Nullable getResources()55 Resources getResources() { return null; } 56 57 /* @hide */ getDensity()58 int getDensity() { return Bitmap.DENSITY_NONE; } 59 60 /* @hide */ computeDstDensity()61 int computeDstDensity() { 62 Resources res = getResources(); 63 if (res == null) { 64 return Bitmap.getDefaultDensity(); 65 } 66 67 return res.getDisplayMetrics().densityDpi; 68 } 69 70 /* @hide */ 71 @NonNull createImageDecoder()72 abstract ImageDecoder createImageDecoder() throws IOException; 73 }; 74 75 private static class ByteArraySource extends Source { ByteArraySource(@onNull byte[] data, int offset, int length)76 ByteArraySource(@NonNull byte[] data, int offset, int length) { 77 mData = data; 78 mOffset = offset; 79 mLength = length; 80 }; 81 private final byte[] mData; 82 private final int mOffset; 83 private final int mLength; 84 85 @Override createImageDecoder()86 public ImageDecoder createImageDecoder() throws IOException { 87 return new ImageDecoder(); 88 } 89 } 90 91 private static class ByteBufferSource extends Source { ByteBufferSource(@onNull ByteBuffer buffer)92 ByteBufferSource(@NonNull ByteBuffer buffer) { 93 mBuffer = buffer; 94 } 95 private final ByteBuffer mBuffer; 96 97 @Override createImageDecoder()98 public ImageDecoder createImageDecoder() throws IOException { 99 return new ImageDecoder(); 100 } 101 } 102 103 private static class ContentResolverSource extends Source { ContentResolverSource(@onNull ContentResolver resolver, @NonNull Uri uri)104 ContentResolverSource(@NonNull ContentResolver resolver, @NonNull Uri uri) { 105 mResolver = resolver; 106 mUri = uri; 107 } 108 109 private final ContentResolver mResolver; 110 private final Uri mUri; 111 112 @Override createImageDecoder()113 public ImageDecoder createImageDecoder() throws IOException { 114 return new ImageDecoder(); 115 } 116 } 117 118 /** 119 * For backwards compatibility, this does *not* close the InputStream. 120 */ 121 private static class InputStreamSource extends Source { InputStreamSource(Resources res, InputStream is, int inputDensity)122 InputStreamSource(Resources res, InputStream is, int inputDensity) { 123 if (is == null) { 124 throw new IllegalArgumentException("The InputStream cannot be null"); 125 } 126 mResources = res; 127 mInputStream = is; 128 mInputDensity = res != null ? inputDensity : Bitmap.DENSITY_NONE; 129 } 130 131 final Resources mResources; 132 InputStream mInputStream; 133 final int mInputDensity; 134 135 @Override getResources()136 public Resources getResources() { return mResources; } 137 138 @Override getDensity()139 public int getDensity() { return mInputDensity; } 140 141 @Override createImageDecoder()142 public ImageDecoder createImageDecoder() throws IOException { 143 return new ImageDecoder(); 144 } 145 } 146 147 /** 148 * Takes ownership of the AssetInputStream. 149 * 150 * @hide 151 */ 152 public static class AssetInputStreamSource extends Source { AssetInputStreamSource(@onNull AssetInputStream ais, @NonNull Resources res, @NonNull TypedValue value)153 public AssetInputStreamSource(@NonNull AssetInputStream ais, 154 @NonNull Resources res, @NonNull TypedValue value) { 155 mAssetInputStream = ais; 156 mResources = res; 157 158 if (value.density == TypedValue.DENSITY_DEFAULT) { 159 mDensity = DisplayMetrics.DENSITY_DEFAULT; 160 } else if (value.density != TypedValue.DENSITY_NONE) { 161 mDensity = value.density; 162 } else { 163 mDensity = Bitmap.DENSITY_NONE; 164 } 165 } 166 167 private AssetInputStream mAssetInputStream; 168 private final Resources mResources; 169 private final int mDensity; 170 171 @Override getResources()172 public Resources getResources() { return mResources; } 173 174 @Override getDensity()175 public int getDensity() { 176 return mDensity; 177 } 178 179 @Override createImageDecoder()180 public ImageDecoder createImageDecoder() throws IOException { 181 return new ImageDecoder(); 182 } 183 } 184 185 private static class ResourceSource extends Source { ResourceSource(@onNull Resources res, int resId)186 ResourceSource(@NonNull Resources res, int resId) { 187 mResources = res; 188 mResId = resId; 189 mResDensity = Bitmap.DENSITY_NONE; 190 } 191 192 final Resources mResources; 193 final int mResId; 194 int mResDensity; 195 196 @Override getResources()197 public Resources getResources() { return mResources; } 198 199 @Override getDensity()200 public int getDensity() { return mResDensity; } 201 202 @Override createImageDecoder()203 public ImageDecoder createImageDecoder() throws IOException { 204 return new ImageDecoder(); 205 } 206 } 207 208 private static class FileSource extends Source { FileSource(@onNull File file)209 FileSource(@NonNull File file) { 210 mFile = file; 211 } 212 213 private final File mFile; 214 215 @Override createImageDecoder()216 public ImageDecoder createImageDecoder() throws IOException { 217 return new ImageDecoder(); 218 } 219 } 220 221 /** 222 * Contains information about the encoded image. 223 */ 224 public static class ImageInfo { 225 private ImageDecoder mDecoder; 226 ImageInfo(@onNull ImageDecoder decoder)227 private ImageInfo(@NonNull ImageDecoder decoder) { 228 mDecoder = decoder; 229 } 230 231 /** 232 * Size of the image, without scaling or cropping. 233 */ 234 @NonNull getSize()235 public Size getSize() { 236 return new Size(0, 0); 237 } 238 239 /** 240 * The mimeType of the image. 241 */ 242 @NonNull getMimeType()243 public String getMimeType() { 244 return ""; 245 } 246 247 /** 248 * Whether the image is animated. 249 * 250 * <p>Calling {@link #decodeDrawable} will return an 251 * {@link AnimatedImageDrawable}.</p> 252 */ isAnimated()253 public boolean isAnimated() { 254 return mDecoder.mAnimated; 255 } 256 }; 257 258 /** 259 * Thrown if the provided data is incomplete. 260 */ 261 public static class IncompleteException extends IOException {}; 262 263 /** 264 * Optional listener supplied to {@link #decodeDrawable} or 265 * {@link #decodeBitmap}. 266 */ 267 public interface OnHeaderDecodedListener { 268 /** 269 * Called when the header is decoded and the size is known. 270 * 271 * @param decoder allows changing the default settings of the decode. 272 * @param info Information about the encoded image. 273 * @param source that created the decoder. 274 */ onHeaderDecoded(@onNull ImageDecoder decoder, @NonNull ImageInfo info, @NonNull Source source)275 void onHeaderDecoded(@NonNull ImageDecoder decoder, 276 @NonNull ImageInfo info, @NonNull Source source); 277 278 }; 279 280 /** 281 * An Exception was thrown reading the {@link Source}. 282 */ 283 public static final int ERROR_SOURCE_EXCEPTION = 1; 284 285 /** 286 * The encoded data was incomplete. 287 */ 288 public static final int ERROR_SOURCE_INCOMPLETE = 2; 289 290 /** 291 * The encoded data contained an error. 292 */ 293 public static final int ERROR_SOURCE_ERROR = 3; 294 295 @Retention(SOURCE) 296 public @interface Error {} 297 298 /** 299 * Optional listener supplied to the ImageDecoder. 300 * 301 * Without this listener, errors will throw {@link java.io.IOException}. 302 */ 303 public interface OnPartialImageListener { 304 /** 305 * Called when there is only a partial image to display. 306 * 307 * If decoding is interrupted after having decoded a partial image, 308 * this listener lets the client know that and allows them to 309 * optionally finish the rest of the decode/creation process to create 310 * a partial {@link Drawable}/{@link Bitmap}. 311 * 312 * @param error indicating what interrupted the decode. 313 * @param source that had the error. 314 * @return True to create and return a {@link Drawable}/{@link Bitmap} 315 * with partial data. False (which is the default) to abort the 316 * decode and throw {@link java.io.IOException}. 317 */ onPartialImage(@rror int error, @NonNull Source source)318 boolean onPartialImage(@Error int error, @NonNull Source source); 319 } 320 321 private boolean mAnimated; 322 private Rect mOutPaddingRect; 323 ImageDecoder()324 public ImageDecoder() { 325 mAnimated = true; // This is too avoid throwing an exception in AnimatedImageDrawable 326 } 327 328 /** 329 * Create a new {@link Source} from an asset. 330 * @hide 331 * 332 * @param res the {@link Resources} object containing the image data. 333 * @param resId resource ID of the image data. 334 * // FIXME: Can be an @DrawableRes? 335 * @return a new Source object, which can be passed to 336 * {@link #decodeDrawable} or {@link #decodeBitmap}. 337 */ 338 @NonNull createSource(@onNull Resources res, int resId)339 public static Source createSource(@NonNull Resources res, int resId) 340 { 341 return new ResourceSource(res, resId); 342 } 343 344 /** 345 * Create a new {@link Source} from a {@link android.net.Uri}. 346 * 347 * @param cr to retrieve from. 348 * @param uri of the image file. 349 * @return a new Source object, which can be passed to 350 * {@link #decodeDrawable} or {@link #decodeBitmap}. 351 */ 352 @NonNull createSource(@onNull ContentResolver cr, @NonNull Uri uri)353 public static Source createSource(@NonNull ContentResolver cr, 354 @NonNull Uri uri) { 355 return new ContentResolverSource(cr, uri); 356 } 357 358 /** 359 * Create a new {@link Source} from a byte array. 360 * 361 * @param data byte array of compressed image data. 362 * @param offset offset into data for where the decoder should begin 363 * parsing. 364 * @param length number of bytes, beginning at offset, to parse. 365 * @throws NullPointerException if data is null. 366 * @throws ArrayIndexOutOfBoundsException if offset and length are 367 * not within data. 368 * @hide 369 */ 370 @NonNull createSource(@onNull byte[] data, int offset, int length)371 public static Source createSource(@NonNull byte[] data, int offset, 372 int length) throws ArrayIndexOutOfBoundsException { 373 if (offset < 0 || length < 0 || offset >= data.length || 374 offset + length > data.length) { 375 throw new ArrayIndexOutOfBoundsException( 376 "invalid offset/length!"); 377 } 378 return new ByteArraySource(data, offset, length); 379 } 380 381 /** 382 * See {@link #createSource(byte[], int, int). 383 * @hide 384 */ 385 @NonNull createSource(@onNull byte[] data)386 public static Source createSource(@NonNull byte[] data) { 387 return createSource(data, 0, data.length); 388 } 389 390 /** 391 * Create a new {@link Source} from a {@link java.nio.ByteBuffer}. 392 * 393 * <p>The returned {@link Source} effectively takes ownership of the 394 * {@link java.nio.ByteBuffer}; i.e. no other code should modify it after 395 * this call.</p> 396 * 397 * Decoding will start from {@link java.nio.ByteBuffer#position()}. The 398 * position after decoding is undefined. 399 */ 400 @NonNull createSource(@onNull ByteBuffer buffer)401 public static Source createSource(@NonNull ByteBuffer buffer) { 402 return new ByteBufferSource(buffer); 403 } 404 405 /** 406 * Internal API used to generate bitmaps for use by Drawables (i.e. BitmapDrawable) 407 * @hide 408 */ createSource(Resources res, InputStream is)409 public static Source createSource(Resources res, InputStream is) { 410 return new InputStreamSource(res, is, Bitmap.getDefaultDensity()); 411 } 412 413 /** 414 * Internal API used to generate bitmaps for use by Drawables (i.e. BitmapDrawable) 415 * @hide 416 */ createSource(Resources res, InputStream is, int density)417 public static Source createSource(Resources res, InputStream is, int density) { 418 return new InputStreamSource(res, is, density); 419 } 420 421 /** 422 * Create a new {@link Source} from a {@link java.io.File}. 423 */ 424 @NonNull createSource(@onNull File file)425 public static Source createSource(@NonNull File file) { 426 return new FileSource(file); 427 } 428 429 /** 430 * Return the width and height of a given sample size. 431 * 432 * <p>This takes an input that functions like 433 * {@link BitmapFactory.Options#inSampleSize}. It returns a width and 434 * height that can be acheived by sampling the encoded image. Other widths 435 * and heights may be supported, but will require an additional (internal) 436 * scaling step. Such internal scaling is *not* supported with 437 * {@link #setRequireUnpremultiplied} set to {@code true}.</p> 438 * 439 * @param sampleSize Sampling rate of the encoded image. 440 * @return {@link android.util.Size} of the width and height after 441 * sampling. 442 */ 443 @NonNull getSampledSize(int sampleSize)444 public Size getSampledSize(int sampleSize) { 445 return new Size(0, 0); 446 } 447 448 // Modifiers 449 /** 450 * Resize the output to have the following size. 451 * 452 * @param width must be greater than 0. 453 * @param height must be greater than 0. 454 */ setResize(int width, int height)455 public void setResize(int width, int height) { 456 } 457 458 /** 459 * Resize based on a sample size. 460 * 461 * <p>This has the same effect as passing the result of 462 * {@link #getSampledSize} to {@link #setResize(int, int)}.</p> 463 * 464 * @param sampleSize Sampling rate of the encoded image. 465 */ setResize(int sampleSize)466 public void setResize(int sampleSize) { 467 } 468 469 // These need to stay in sync with ImageDecoder.cpp's Allocator enum. 470 /** 471 * Use the default allocation for the pixel memory. 472 * 473 * Will typically result in a {@link Bitmap.Config#HARDWARE} 474 * allocation, but may be software for small images. In addition, this will 475 * switch to software when HARDWARE is incompatible, e.g. 476 * {@link #setMutable}, {@link #setAsAlphaMask}. 477 */ 478 public static final int ALLOCATOR_DEFAULT = 0; 479 480 /** 481 * Use a software allocation for the pixel memory. 482 * 483 * Useful for drawing to a software {@link Canvas} or for 484 * accessing the pixels on the final output. 485 */ 486 public static final int ALLOCATOR_SOFTWARE = 1; 487 488 /** 489 * Use shared memory for the pixel memory. 490 * 491 * Useful for sharing across processes. 492 */ 493 public static final int ALLOCATOR_SHARED_MEMORY = 2; 494 495 /** 496 * Require a {@link Bitmap.Config#HARDWARE} {@link Bitmap}. 497 * 498 * When this is combined with incompatible options, like 499 * {@link #setMutable} or {@link #setAsAlphaMask}, {@link #decodeDrawable} 500 * / {@link #decodeBitmap} will throw an 501 * {@link java.lang.IllegalStateException}. 502 */ 503 public static final int ALLOCATOR_HARDWARE = 3; 504 505 /** @hide **/ 506 @Retention(SOURCE) 507 public @interface Allocator {}; 508 509 /** 510 * Choose the backing for the pixel memory. 511 * 512 * This is ignored for animated drawables. 513 * 514 * @param allocator Type of allocator to use. 515 */ setAllocator(@llocator int allocator)516 public void setAllocator(@Allocator int allocator) { } 517 518 /** 519 * Specify whether the {@link Bitmap} should have unpremultiplied pixels. 520 * 521 * By default, ImageDecoder will create a {@link Bitmap} with 522 * premultiplied pixels, which is required for drawing with the 523 * {@link android.view.View} system (i.e. to a {@link Canvas}). Calling 524 * this method with a value of {@code true} will result in 525 * {@link #decodeBitmap} returning a {@link Bitmap} with unpremultiplied 526 * pixels. See {@link Bitmap#isPremultiplied}. This is incompatible with 527 * {@link #decodeDrawable}; attempting to decode an unpremultiplied 528 * {@link Drawable} will throw an {@link java.lang.IllegalStateException}. 529 */ setRequireUnpremultiplied(boolean requireUnpremultiplied)530 public ImageDecoder setRequireUnpremultiplied(boolean requireUnpremultiplied) { 531 return this; 532 } 533 534 /** 535 * Modify the image after decoding and scaling. 536 * 537 * <p>This allows adding effects prior to returning a {@link Drawable} or 538 * {@link Bitmap}. For a {@code Drawable} or an immutable {@code Bitmap}, 539 * this is the only way to process the image after decoding.</p> 540 * 541 * <p>If set on a nine-patch image, the nine-patch data is ignored.</p> 542 * 543 * <p>For an animated image, the drawing commands drawn on the 544 * {@link Canvas} will be recorded immediately and then applied to each 545 * frame.</p> 546 */ setPostProcessor(@ullable PostProcessor p)547 public void setPostProcessor(@Nullable PostProcessor p) { } 548 549 /** 550 * Set (replace) the {@link OnPartialImageListener} on this object. 551 * 552 * Will be called if there is an error in the input. Without one, a 553 * partial {@link Bitmap} will be created. 554 */ setOnPartialImageListener(@ullable OnPartialImageListener l)555 public void setOnPartialImageListener(@Nullable OnPartialImageListener l) { } 556 557 /** 558 * Crop the output to {@code subset} of the (possibly) scaled image. 559 * 560 * <p>{@code subset} must be contained within the size set by 561 * {@link #setResize} or the bounds of the image if setResize was not 562 * called. Otherwise an {@link IllegalStateException} will be thrown by 563 * {@link #decodeDrawable}/{@link #decodeBitmap}.</p> 564 * 565 * <p>NOT intended as a replacement for 566 * {@link BitmapRegionDecoder#decodeRegion}. This supports all formats, 567 * but merely crops the output.</p> 568 */ setCrop(@ullable Rect subset)569 public void setCrop(@Nullable Rect subset) { } 570 571 /** 572 * Set a Rect for retrieving nine patch padding. 573 * 574 * If the image is a nine patch, this Rect will be set to the padding 575 * rectangle during decode. Otherwise it will not be modified. 576 * 577 * @hide 578 */ setOutPaddingRect(@onNull Rect outPadding)579 public void setOutPaddingRect(@NonNull Rect outPadding) { 580 mOutPaddingRect = outPadding; 581 } 582 583 /** 584 * Specify whether the {@link Bitmap} should be mutable. 585 * 586 * <p>By default, a {@link Bitmap} created will be immutable, but that can 587 * be changed with this call.</p> 588 * 589 * <p>Mutable Bitmaps are incompatible with {@link #ALLOCATOR_HARDWARE}, 590 * because {@link Bitmap.Config#HARDWARE} Bitmaps cannot be mutable. 591 * Attempting to combine them will throw an 592 * {@link java.lang.IllegalStateException}.</p> 593 * 594 * <p>Mutable Bitmaps are also incompatible with {@link #decodeDrawable}, 595 * which would require retrieving the Bitmap from the returned Drawable in 596 * order to modify. Attempting to decode a mutable {@link Drawable} will 597 * throw an {@link java.lang.IllegalStateException}.</p> 598 */ setMutable(boolean mutable)599 public ImageDecoder setMutable(boolean mutable) { 600 return this; 601 } 602 603 /** 604 * Specify whether to potentially save RAM at the expense of quality. 605 * 606 * Setting this to {@code true} may result in a {@link Bitmap} with a 607 * denser {@link Bitmap.Config}, depending on the image. For example, for 608 * an opaque {@link Bitmap}, this may result in a {@link Bitmap.Config} 609 * with no alpha information. 610 */ setPreferRamOverQuality(boolean preferRamOverQuality)611 public ImageDecoder setPreferRamOverQuality(boolean preferRamOverQuality) { 612 return this; 613 } 614 615 /** 616 * Specify whether to potentially treat the output as an alpha mask. 617 * 618 * <p>If this is set to {@code true} and the image is encoded in a format 619 * with only one channel, treat that channel as alpha. Otherwise this call has 620 * no effect.</p> 621 * 622 * <p>setAsAlphaMask is incompatible with {@link #ALLOCATOR_HARDWARE}. Trying to 623 * combine them will result in {@link #decodeDrawable}/ 624 * {@link #decodeBitmap} throwing an 625 * {@link java.lang.IllegalStateException}.</p> 626 */ setAsAlphaMask(boolean asAlphaMask)627 public ImageDecoder setAsAlphaMask(boolean asAlphaMask) { 628 return this; 629 } 630 631 @Override close()632 public void close() { 633 } 634 635 /** 636 * Create a {@link Drawable} from a {@code Source}. 637 * 638 * @param src representing the encoded image. 639 * @param listener for learning the {@link ImageInfo} and changing any 640 * default settings on the {@code ImageDecoder}. If not {@code null}, 641 * this will be called on the same thread as {@code decodeDrawable} 642 * before that method returns. 643 * @return Drawable for displaying the image. 644 * @throws IOException if {@code src} is not found, is an unsupported 645 * format, or cannot be decoded for any reason. 646 */ 647 @NonNull decodeDrawable(@onNull Source src, @Nullable OnHeaderDecodedListener listener)648 public static Drawable decodeDrawable(@NonNull Source src, 649 @Nullable OnHeaderDecodedListener listener) throws IOException { 650 Bitmap bitmap = decodeBitmap(src, listener); 651 return new BitmapDrawable(src.getResources(), bitmap); 652 } 653 654 /** 655 * See {@link #decodeDrawable(Source, OnHeaderDecodedListener)}. 656 */ 657 @NonNull decodeDrawable(@onNull Source src)658 public static Drawable decodeDrawable(@NonNull Source src) 659 throws IOException { 660 return decodeDrawable(src, null); 661 } 662 663 /** 664 * Create a {@link Bitmap} from a {@code Source}. 665 * 666 * @param src representing the encoded image. 667 * @param listener for learning the {@link ImageInfo} and changing any 668 * default settings on the {@code ImageDecoder}. If not {@code null}, 669 * this will be called on the same thread as {@code decodeBitmap} 670 * before that method returns. 671 * @return Bitmap containing the image. 672 * @throws IOException if {@code src} is not found, is an unsupported 673 * format, or cannot be decoded for any reason. 674 */ 675 @NonNull decodeBitmap(@onNull Source src, @Nullable OnHeaderDecodedListener listener)676 public static Bitmap decodeBitmap(@NonNull Source src, 677 @Nullable OnHeaderDecodedListener listener) throws IOException { 678 TypedValue value = new TypedValue(); 679 value.density = src.getDensity(); 680 ImageDecoder decoder = src.createImageDecoder(); 681 if (listener != null) { 682 listener.onHeaderDecoded(decoder, new ImageInfo(decoder), src); 683 } 684 return BitmapFactory.decodeResourceStream(src.getResources(), value, 685 ((InputStreamSource) src).mInputStream, decoder.mOutPaddingRect, null); 686 } 687 688 /** 689 * See {@link #decodeBitmap(Source, OnHeaderDecodedListener)}. 690 */ 691 @NonNull decodeBitmap(@onNull Source src)692 public static Bitmap decodeBitmap(@NonNull Source src) throws IOException { 693 return decodeBitmap(src, null); 694 } 695 696 public static final class DecodeException extends IOException { 697 /** 698 * An Exception was thrown reading the {@link Source}. 699 */ 700 public static final int SOURCE_EXCEPTION = 1; 701 702 /** 703 * The encoded data was incomplete. 704 */ 705 public static final int SOURCE_INCOMPLETE = 2; 706 707 /** 708 * The encoded data contained an error. 709 */ 710 public static final int SOURCE_MALFORMED_DATA = 3; 711 712 @Error final int mError; 713 @NonNull final Source mSource; 714 DecodeException(@rror int error, @Nullable Throwable cause, @NonNull Source source)715 DecodeException(@Error int error, @Nullable Throwable cause, @NonNull Source source) { 716 super(errorMessage(error, cause), cause); 717 mError = error; 718 mSource = source; 719 } 720 721 /** 722 * Private method called by JNI. 723 */ 724 @SuppressWarnings("unused") DecodeException(@rror int error, @Nullable String msg, @Nullable Throwable cause, @NonNull Source source)725 DecodeException(@Error int error, @Nullable String msg, @Nullable Throwable cause, 726 @NonNull Source source) { 727 super(msg + errorMessage(error, cause), cause); 728 mError = error; 729 mSource = source; 730 } 731 732 /** 733 * Retrieve the reason that decoding was interrupted. 734 * 735 * <p>If the error is {@link #SOURCE_EXCEPTION}, the underlying 736 * {@link java.lang.Throwable} can be retrieved with 737 * {@link java.lang.Throwable#getCause}.</p> 738 */ 739 @Error getError()740 public int getError() { 741 return mError; 742 } 743 744 /** 745 * Retrieve the {@link Source Source} that was interrupted. 746 * 747 * <p>This can be used for equality checking to find the Source which 748 * failed to completely decode.</p> 749 */ 750 @NonNull getSource()751 public Source getSource() { 752 return mSource; 753 } 754 errorMessage(@rror int error, @Nullable Throwable cause)755 private static String errorMessage(@Error int error, @Nullable Throwable cause) { 756 switch (error) { 757 case SOURCE_EXCEPTION: 758 return "Exception in input: " + cause; 759 case SOURCE_INCOMPLETE: 760 return "Input was incomplete."; 761 case SOURCE_MALFORMED_DATA: 762 return "Input contained an error."; 763 default: 764 return ""; 765 } 766 } 767 } 768 } 769