gt:         while (mExecutor.getQueue().remove(mRenderTask)) ;
context: package pl.droidsonroids.gif;
  
  import android.annotation.TargetApi;
  import android.content.ContentResolver;
  import android.content.res.AssetFileDescriptor;
  import android.content.res.AssetManager;
  import android.content.res.ColorStateList;
  import android.content.res.Resources;
  import android.content.res.Resources.NotFoundException;
  import android.graphics.Bitmap;
  import android.graphics.Canvas;
  import android.graphics.Color;
  import android.graphics.ColorFilter;
  import android.graphics.Paint;
  import android.graphics.PixelFormat;
  import android.graphics.PorterDuff;
  import android.graphics.PorterDuffColorFilter;
  import android.graphics.Rect;
  import android.graphics.drawable.Animatable;
  import android.graphics.drawable.Drawable;
  import android.net.Uri;
  import android.os.Build;
  import android.os.StrictMode;
  import android.os.SystemClock;
  import android.support.annotation.DrawableRes;
  import android.support.annotation.NonNull;
  import android.support.annotation.Nullable;
  import android.support.annotation.RawRes;
  import android.widget.MediaController.MediaPlayerControl;
  
  import java.io.File;
  import java.io.FileDescriptor;
  import java.io.IOException;
  import java.io.InputStream;
  import java.nio.ByteBuffer;
  import java.util.Locale;
  import java.util.concurrent.ConcurrentLinkedQueue;
  import java.util.concurrent.ScheduledThreadPoolExecutor;
  import java.util.concurrent.TimeUnit;
  
  /**
   * A {@link Drawable} which can be used to hold GIF images, especially animations.
   * Basic GIF metadata can also be examined.
   *
   * @author koral--
   */
  public class GifDrawable extends Drawable implements Animatable, MediaPlayerControl {
      final ScheduledThreadPoolExecutor mExecutor;
  
      volatile boolean mIsRunning = true;
      long mNextFrameRenderTime = Long.MIN_VALUE;
  
      private final Rect mDstRect = new Rect();
      /**
       * Paint used to draw on a Canvas
       */
      protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
      /**
       * Frame buffer, holds current frame.
       */
      final Bitmap mBuffer;
      final GifInfoHandle mNativeInfoHandle;
      final ConcurrentLinkedQueue<AnimationListener> mListeners = new ConcurrentLinkedQueue<>();
      private ColorStateList mTint;
      private PorterDuffColorFilter mTintFilter;
      private PorterDuff.Mode mTintMode;
      final boolean mIsRenderingTriggeredOnDraw;
      final InvalidationHandler mInvalidationHandler;
  
      private final Runnable mRenderTask = new RenderTask(this);
      private final Rect mSrcRect;
  
      /**
       * Creates drawable from resource.
       *
       * @param res Resources to read from
       * @param id  resource id (raw or drawable)
       * @throws NotFoundException    if the given ID does not exist.
       * @throws IOException          when opening failed
       * @throws NullPointerException if res is null
       */
      public GifDrawable(@NonNull Resources res, @DrawableRes @RawRes int id) throws NotFoundException, IOException {
          this(res.openRawResourceFd(id));
      }
  
      /**
       * Creates drawable from asset.
       *
       * @param assets    AssetManager to read from
       * @param assetName name of the asset
       * @throws IOException          when opening failed
       * @throws NullPointerException if assets or assetName is null
       */
      public GifDrawable(@NonNull AssetManager assets, @NonNull String assetName) throws IOException {
          this(assets.openFd(assetName));
      }
  
      /**
       * Constructs drawable from given file path.<br>
       * Only metadata is read, no graphic data is decoded here.
       * In practice can be called from main thread. However it will violate
       * {@link StrictMode} policy if disk reads detection is enabled.<br>
       *
       * @param filePath path to the GIF file
       * @throws IOException          when opening failed
       * @throws NullPointerException if filePath is null
       */
      public GifDrawable(@NonNull String filePath) throws IOException {
          this(GifInfoHandle.openFile(filePath, false), null, null, true);
      }
  
      /**
       * Equivalent to {@code} GifDrawable(file.getPath())}
       *
       * @param file the GIF file
       * @throws IOException          when opening failed
       * @throws NullPointerException if file is null
       */
      public GifDrawable(@NonNull File file) throws IOException {
          this(GifInfoHandle.openFile(file.getPath(), false), null, null, true);
      }
  
      /**
       * Creates drawable from InputStream.
       * InputStream must support marking, IllegalArgumentException will be thrown otherwise.
       *
       * @param stream stream to read from
       * @throws IOException              when opening failed
       * @throws IllegalArgumentException if stream does not support marking
       * @throws NullPointerException     if stream is null
       */
      public GifDrawable(@NonNull InputStream stream) throws IOException {
          this(GifInfoHandle.openMarkableInputStream(stream, false), null, null, true);
      }
  
      /**
       * Creates drawable from AssetFileDescriptor.
       * Convenience wrapper for {@link GifDrawable#GifDrawable(FileDescriptor)}
       *
       * @param afd source
       * @throws NullPointerException if afd is null
       * @throws IOException          when opening failed
       */
      public GifDrawable(@NonNull AssetFileDescriptor afd) throws IOException {
          this(GifInfoHandle.openAssetFileDescriptor(afd, false), null, null, true);
      }
  
      /**
       * Creates drawable from FileDescriptor
       *
       * @param fd source
       * @throws IOException          when opening failed
       * @throws NullPointerException if fd is null
       */
      public GifDrawable(@NonNull FileDescriptor fd) throws IOException {
          this(GifInfoHandle.openFd(fd, 0, false), null, null, true);
      }
  
      /**
       * Creates drawable from byte array.<br>
       * It can be larger than size of the GIF data. Bytes beyond GIF terminator are not accessed.
       *
       * @param bytes raw GIF bytes
       * @throws IOException          if bytes does not contain valid GIF data
       * @throws NullPointerException if bytes are null
       */
      public GifDrawable(@NonNull byte[] bytes) throws IOException {
          this(GifInfoHandle.openByteArray(bytes, false), null, null, true);
      }
  
      /**
       * Creates drawable from {@link ByteBuffer}. Only direct buffers are supported.
       * Buffer can be larger than size of the GIF data. Bytes beyond GIF terminator are not accessed.
       *
       * @param buffer buffer containing GIF data
       * @throws IOException          if buffer does not contain valid GIF data or is indirect
       * @throws NullPointerException if buffer is null
       */
      public GifDrawable(@NonNull ByteBuffer buffer) throws IOException {
          this(GifInfoHandle.openDirectByteBuffer(buffer, false), null, null, true);
      }
  
      /**
       * Creates drawable from {@link android.net.Uri} which is resolved using {@code resolver}.
       * {@link android.content.ContentResolver#openAssetFileDescriptor(android.net.Uri, String)}
       * is used to open an Uri.
       *
       * @param uri      GIF Uri, cannot be null.
       * @param resolver resolver used to query {@code uri}, can be null for file:// scheme Uris
       * @throws IOException if resolution fails or destination is not a GIF.
       */
      public GifDrawable(@Nullable ContentResolver resolver, @NonNull Uri uri) throws IOException {
          this(GifInfoHandle.openUri(resolver, uri, false), null, null, true);
      }
  
      GifDrawable(GifInfoHandle gifInfoHandle, final GifDrawable oldDrawable, ScheduledThreadPoolExecutor executor, boolean isRenderingTriggeredOnDraw) {
          mIsRenderingTriggeredOnDraw = isRenderingTriggeredOnDraw;
          mExecutor = executor != null ? executor : GifRenderingExecutor.getInstance();
          mNativeInfoHandle = gifInfoHandle;
          Bitmap oldBitmap = null;
          if (oldDrawable != null) {
              synchronized (oldDrawable.mNativeInfoHandle) {
                  if (!oldDrawable.mNativeInfoHandle.isRecycled()) {
                      if (oldDrawable.mNativeInfoHandle.height >= mNativeInfoHandle.height && oldDrawable.mNativeInfoHandle.width >= mNativeInfoHandle.width) {
                          oldDrawable.shutdown();
                          oldBitmap = oldDrawable.mBuffer;
                          oldBitmap.eraseColor(Color.TRANSPARENT);
                      }
                  }
              }
          }
  
          if (oldBitmap == null) {
              mBuffer = Bitmap.createBitmap(mNativeInfoHandle.width, mNativeInfoHandle.height, Bitmap.Config.ARGB_8888);
          } else {
              mBuffer = oldBitmap;
          }
  
          mSrcRect = new Rect(0, 0, mNativeInfoHandle.width, mNativeInfoHandle.height);
          mInvalidationHandler = new InvalidationHandler(this);
          if (mIsRenderingTriggeredOnDraw) {
              mNextFrameRenderTime = 0;
          } else {
              mExecutor.execute(mRenderTask);
          }
      }
  
      /**
       * Frees any memory allocated native way.
       * Operation is irreversible. After this call, nothing will be drawn.
       * This method is idempotent, subsequent calls have no effect.
       * Like {@link android.graphics.Bitmap#recycle()} this is an advanced call and
       * is invoked implicitly by finalizer.
       */
      public void recycle() {
          shutdown();
          mBuffer.recycle();
      }
  
      private void shutdown() {
          mIsRunning = false;
          mInvalidationHandler.removeMessages(0);
          mNativeInfoHandle.recycle();
      }
  
      /**
       * @return true if drawable is recycled
       */
      public boolean isRecycled() {
          return mNativeInfoHandle.isRecycled();
      }
  
      @Override
      public int getIntrinsicHeight() {
          return mNativeInfoHandle.height;
      }
  
      @Override
      public int getIntrinsicWidth() {
          return mNativeInfoHandle.width;
      }
  
      @Override
      public void setAlpha(int alpha) {
          mPaint.setAlpha(alpha);
      }
  
      @Override
      public void setColorFilter(@Nullable ColorFilter cf) {
          mPaint.setColorFilter(cf);
      }
  
      /**
       * See {@link Drawable#getOpacity()}
       *
       * @return always {@link PixelFormat#TRANSPARENT}
       */
      @Override
      public int getOpacity() {
          return PixelFormat.TRANSPARENT;
      }
  
      /**
       * Starts the animation. Does nothing if GIF is not animated.
       * This method is thread-safe.
       */
      @Override
      public void start() {
          mIsRunning = true;
          final long lastFrameRemainder = mNativeInfoHandle.restoreRemainder();
          startAnimation(lastFrameRemainder);
      }
  
      void startAnimation(long lastFrameRemainder) {
          if (lastFrameRemainder >= 0) {
              if (mIsRenderingTriggeredOnDraw) {
                  mNextFrameRenderTime = 0;
                  mInvalidationHandler.sendEmptyMessageAtTime(0, 0);
              } else {
                  //noinspection StatementWithEmptyBody
                  while (mExecutor.getQueue().remove(mRenderTask)) ;
                  mExecutor.schedule(mRenderTask, lastFrameRemainder, TimeUnit.MILLISECONDS);
              }
          }
      }
  
      /**
       * Causes the animation to start over.
       * If rewinding input source fails then state is not affected.
       * This method is thread-safe.
       */
      public void reset() {
          mExecutor.execute(new SafeRunnable(this) {
              @Override
              public void doWork() {
                  if (mNativeInfoHandle.reset()) {
                      start();
                  }
              }
          });
      }
  
      /**
       * Stops the animation. Does nothing if GIF is not animated.
       * This method is thread-safe.
       */
      @Override
      public void stop() {
          mIsRunning = false;
          mInvalidationHandler.removeMessages(0);
          //noinspection StatementWithEmptyBody
  