Gallery2: Play gif photo directly in photopage

Implement new feature: Remove play icon and play the gif photo directly

Change-Id: I95f82ca8ab912a98a5120ce2c01fe2fe33c9a875
CRs-Fixed: 987942
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 08ca742..1eda90b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (c) 2010-2014, The Linux Foundation. All rights reserved.
+<!-- Copyright (c) 2010-2014, 2016, The Linux Foundation. All rights reserved.
      Not a Contribution.
 
      Copyright (C) 2007 The Android Open Source Project
@@ -328,16 +328,6 @@
                 android:theme="@style/Theme.Gallery"
                 android:configChanges="orientation|keyboardHidden|screenSize" />
 
-        <activity android:name="com.android.gallery3d.util.ViewGifImage"
-                android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
-                android:configChanges="orientation|keyboardHidden|screenSize|keyboard|navigation">
-            <intent-filter>
-                <action android:name="com.android.gallery3d.VIEW_GIF" />
-                <data android:mimeType="image/gif" />
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-        </activity>
-
         <provider android:name="com.android.gallery3d.provider.GalleryProvider"
                 android:syncable="false"
                 android:grantUriPermissions="true"
diff --git a/res/layout/view_gif_image.xml b/res/layout/view_gif_image.xml
deleted file mode 100644
index 976549a..0000000
--- a/res/layout/view_gif_image.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-
-    <LinearLayout
-            android:id="@+id/image_absoluteLayout"
-            android:layout_height="match_parent"
-            android:layout_width="match_parent">
-        <ImageView android:id="@+id/image_display_area"
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:clickable="true">
-        </ImageView>
-    </LinearLayout>
-
-</RelativeLayout>
diff --git a/src/com/android/gallery3d/app/PhotoDataAdapter.java b/src/com/android/gallery3d/app/PhotoDataAdapter.java
index aa28b55..d65c0d1 100755
--- a/src/com/android/gallery3d/app/PhotoDataAdapter.java
+++ b/src/com/android/gallery3d/app/PhotoDataAdapter.java
@@ -34,6 +34,7 @@
 import com.android.gallery3d.data.Path;
 import com.android.gallery3d.data.SnailItem;
 import com.android.gallery3d.glrenderer.TiledTexture;
+import com.android.gallery3d.ui.BitmapScreenNail;
 import com.android.gallery3d.ui.PhotoView;
 import com.android.gallery3d.ui.ScreenNail;
 import com.android.gallery3d.ui.SynchronizedHandler;
@@ -41,11 +42,14 @@
 import com.android.gallery3d.ui.TiledScreenNail;
 import com.android.gallery3d.util.Future;
 import com.android.gallery3d.util.FutureListener;
+import com.android.gallery3d.util.GifDecoder;
+import com.android.gallery3d.util.GifRequest;
 import com.android.gallery3d.util.MediaSetUtils;
 import com.android.gallery3d.util.ThreadPool;
 import com.android.gallery3d.util.ThreadPool.Job;
 import com.android.gallery3d.util.ThreadPool.JobContext;
 
+import java.lang.Exception;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -71,6 +75,7 @@
 
     private static final int BIT_SCREEN_NAIL = 1;
     private static final int BIT_FULL_IMAGE = 2;
+    private static final int BIT_GIF_ANIMATION = 3;
 
     private static final long NOTIFY_DIRTY_WAIT_TIME = 10;
     // sImageFetchSeq is the fetching sequence for images.
@@ -91,7 +96,7 @@
 
     static {
         int k = 0;
-        sImageFetchSeq = new ImageFetch[1 + (IMAGE_CACHE_SIZE - 1) * 2 + 3];
+        sImageFetchSeq = new ImageFetch[1 + (IMAGE_CACHE_SIZE - 1) * 2 + 4];
         sImageFetchSeq[k++] = new ImageFetch(0, BIT_SCREEN_NAIL);
 
         for (int i = 1; i < IMAGE_CACHE_SIZE; ++i) {
@@ -102,6 +107,7 @@
         sImageFetchSeq[k++] = new ImageFetch(0, BIT_FULL_IMAGE);
         sImageFetchSeq[k++] = new ImageFetch(1, BIT_FULL_IMAGE);
         sImageFetchSeq[k++] = new ImageFetch(-1, BIT_FULL_IMAGE);
+        sImageFetchSeq[k++] = new ImageFetch(0, BIT_GIF_ANIMATION);
     }
 
     private final TileImageViewAdapter mTileProvider = new TileImageViewAdapter();
@@ -160,6 +166,7 @@
     private boolean mNeedFullImage;
     private int mFocusHintDirection = FOCUS_HINT_NEXT;
     private Path mFocusHintPath = null;
+    private AbstractGalleryActivity mActivity;
 
     // If Bundle is from widget, it's true, otherwise it's false.
     private boolean mIsFromWidget = false;
@@ -181,6 +188,7 @@
     public PhotoDataAdapter(AbstractGalleryActivity activity, PhotoView view,
             MediaSet mediaSet, Path itemPath, int indexHint, int cameraIndex,
             boolean isPanorama, boolean isStaticCamera) {
+        mActivity = activity;
         mSource = Utils.checkNotNull(mediaSet);
         mPhotoView = Utils.checkNotNull(view);
         mItemPath = Utils.checkNotNull(itemPath);
@@ -470,6 +478,8 @@
         ImageEntry entry = mImageCache.get(item.getPath());
         if (entry == null) return null;
 
+        ScreenNail targetScreenNail = getTargetScreenNail(entry);
+        if (targetScreenNail != null) return targetScreenNail;
         // Create a default ScreenNail if the real one is not available yet,
         // except for camera that a black screen is better than a gray tile.
         if (entry.screenNail == null && !isCamera(offset)) {
@@ -631,7 +641,7 @@
     }
 
     private void updateTileProvider(ImageEntry entry) {
-        ScreenNail screenNail = entry.screenNail;
+        ScreenNail screenNail = getTargetScreenNail(entry);
         BitmapRegionDecoder fullImage = entry.fullImage;
         if (screenNail != null) {
             if (fullImage != null) {
@@ -648,6 +658,15 @@
         }
     }
 
+
+    private ScreenNail getTargetScreenNail(ImageEntry entry) {
+        if (null == entry) return null;
+        if (null != entry.currentGifFrame){
+            return entry.currentGifFrame;
+        }
+        return entry.screenNail;
+    }
+
     private void updateSlidingWindow() {
         // 1. Update the image window
         int start = Utils.clamp(mCurrentIndex - IMAGE_CACHE_SIZE / 2,
@@ -733,6 +752,12 @@
                 entry.fullImageTask = null;
                 entry.requestedFullImage = MediaObject.INVALID_DATA_VERSION;
             }
+
+            if (entry.gifDecoderTask != null && entry.gifDecoderTask != task) {
+                entry.gifDecoderTask.cancel();
+                entry.gifDecoderTask = null;
+                entry.requestedGif = MediaObject.INVALID_DATA_VERSION;
+            }
         }
     }
 
@@ -782,6 +807,23 @@
         }
     }
 
+
+    private class GifDecoderJob implements Job<GifDecoder> {
+        private MediaItem mItem;
+
+        public GifDecoderJob(MediaItem item) {
+            mItem = item;
+        }
+
+        @Override
+        public GifDecoder run(JobContext jc) {
+            if (isTemporaryItem(mItem)) {
+                return null;
+            }
+            return new GifRequest(mItem.getContentUri(), mActivity).run(jc);
+        }
+    }
+
     // Returns true if we think this is a temporary item created by Camera. A
     // temporary item is an image or a video whose data is still being
     // processed, but an incomplete entry is created first in MediaProvider, so
@@ -830,6 +872,9 @@
         } else if (which == BIT_FULL_IMAGE && entry.fullImageTask != null
                 && entry.requestedFullImage == version) {
             return entry.fullImageTask;
+        } else if (which == BIT_GIF_ANIMATION && entry.gifDecoderTask != null
+                && entry.requestedGif == version) {
+            return entry.gifDecoderTask;
         }
 
         if (which == BIT_SCREEN_NAIL && entry.requestedScreenNail != version) {
@@ -850,6 +895,18 @@
             // request full image
             return entry.fullImageTask;
         }
+
+        if (which == BIT_GIF_ANIMATION
+                && (entry.requestedGif != version)
+                && isGif(0)) {
+            entry.requestedGif = version;
+            entry.gifDecoderTask = mThreadPool.submit(
+                    new GifDecoderJob(item),
+                    new GifDecoderListener(item.getPath()));
+            // request gif decoder
+            return entry.gifDecoderTask;
+        }
+
         return null;
     }
 
@@ -879,6 +936,20 @@
                                 item.getWidth(), item.getHeight());
                     }
                 }
+
+                if (Math.abs(i - mCurrentIndex) > 0) {
+                    if (entry.gifDecoderTask != null) {
+                        entry.gifDecoderTask.cancel();
+                        entry.gifDecoderTask = null;
+                    }
+                    entry.gifDecoder = null;
+                    entry.requestedGif = MediaItem.INVALID_DATA_VERSION;
+                    if (null != entry.currentGifFrame) {
+                        //recycle cached gif frame
+                        entry.currentGifFrame.recycle();
+                        entry.currentGifFrame = null;
+                    }
+                }
             } else {
                 entry = new ImageEntry();
                 mImageCache.put(path, entry);
@@ -940,6 +1011,158 @@
         }
     }
 
+    private class GifDecoderListener
+            implements Runnable, FutureListener<GifDecoder> {
+        private final Path mPath;
+        private Future<GifDecoder> mFuture;
+
+        public GifDecoderListener(Path path) {
+            mPath = path;
+        }
+
+        @Override
+        public void onFutureDone(Future<GifDecoder> future) {
+            mFuture = future;
+            if (null != mFuture.get()) {
+                mMainHandler.sendMessage(
+                        mMainHandler.obtainMessage(MSG_RUN_OBJECT, this));
+            }
+        }
+
+        @Override
+        public void run() {
+            updateGifDecoder(mPath, mFuture);
+        }
+    }
+
+    private void updateGifDecoder(Path path,
+                                  Future<GifDecoder> future) {
+
+        ImageEntry entry = mImageCache.get(path);
+        if (entry == null || entry.gifDecoderTask != future) {
+            return;
+        }
+
+        entry.gifDecoderTask = null;
+        entry.gifDecoder = future.get();
+
+        playGif(path);
+        updateImageRequests();
+
+    }
+
+    private void playGif(Path path) {
+        ImageEntry entry = mImageCache.get(path);
+        if (null == entry) return;
+        if (entry.gifDecoder != null && entry.gifDecoder.getFrameCount() != 0) {
+            if (entry.gifDecoder.mWidth <= 0 || entry.gifDecoder.mHeight <= 0) {
+                return;
+            }
+
+            int currentIndex = mCurrentIndex;
+            if (path == getPath(currentIndex)) {
+                GifEntry gifEntry = new GifEntry();
+                gifEntry.gifDecoder = entry.gifDecoder;
+                gifEntry.animatedIndex = currentIndex;
+                gifEntry.entry = entry;
+
+                mMainHandler.sendMessage(
+                        mMainHandler.obtainMessage(MSG_RUN_OBJECT,
+                                new GifRunnable(path, gifEntry)));
+            }
+        }
+    }
+
+    private class GifRunnable implements Runnable {
+
+        private GifEntry mGifEntry;
+        private Path mPath;
+
+        private void free() {
+            if (null != mGifEntry.gifDecoder) {
+                mGifEntry.gifDecoder.free();
+                mGifEntry.gifDecoder = null;
+            }
+
+            if (null != mGifEntry) {
+                mGifEntry = null;
+            }
+        }
+
+        public GifRunnable(Path path, GifEntry gifEntry) {
+            mPath = path;
+            mGifEntry = gifEntry;
+            if (null == mGifEntry || null == mGifEntry.gifDecoder) {
+                free();
+                return;
+            }
+            boolean imageChanged = mGifEntry.animatedIndex != mCurrentIndex;
+            MediaItem item = getMediaItem(0);
+            Path currentPath = (item != null ? item.getPath() : null);
+            imageChanged |= path != currentPath;
+            if (imageChanged) {
+                free();
+                return;
+            }
+            mGifEntry.currentFrame = 0;
+            mGifEntry.totalFrameCount = mGifEntry.gifDecoder.getFrameCount();
+            if (mGifEntry.totalFrameCount <= 1) {
+                free();
+                return;
+            }
+        }
+
+        @Override
+        public void run() {
+            if (!mIsActive) {
+                free();
+                return;
+            }
+
+            if (null == mGifEntry || null == mGifEntry.gifDecoder) {
+                free();
+                return;
+            }
+
+            boolean imageChanged = mGifEntry.animatedIndex != mCurrentIndex;
+            MediaItem item = getMediaItem(0);
+            Path currentPath = (item != null ? item.getPath() : null);
+            imageChanged |= mPath != currentPath;
+            if (imageChanged) {
+                free();
+                return;
+            }
+
+            Bitmap frameBitmap = mGifEntry.gifDecoder.getFrameImage(mGifEntry.currentFrame);
+            if (null == frameBitmap) {
+                free();
+                return;
+            }
+            long delay = (long) mGifEntry.gifDecoder.getDelay(mGifEntry.currentFrame);
+            mGifEntry.currentFrame = (mGifEntry.currentFrame + 1) % mGifEntry.totalFrameCount;
+
+            ScreenNail gifFrame = new BitmapScreenNail(frameBitmap);
+            if (mGifEntry.entry.currentGifFrame != null) {
+                mGifEntry.entry.currentGifFrame.recycle();
+                mGifEntry.entry.currentGifFrame = null;
+            }
+            mGifEntry.entry.currentGifFrame = gifFrame;
+            updateTileProvider(mGifEntry.entry);
+            mPhotoView.notifyImageChange(0);
+            mMainHandler.sendMessageDelayed(
+                    mMainHandler.obtainMessage(MSG_RUN_OBJECT, this), delay);
+        }
+    }
+
+
+    private static class GifEntry {
+        public ImageEntry entry;
+        public GifDecoder gifDecoder;
+        public int animatedIndex;
+        public int currentFrame;
+        public int totalFrameCount;
+    }
+
     private static class ImageEntry {
         public BitmapRegionDecoder fullImage;
         public ScreenNail screenNail;
@@ -948,6 +1171,11 @@
         public long requestedScreenNail = MediaObject.INVALID_DATA_VERSION;
         public long requestedFullImage = MediaObject.INVALID_DATA_VERSION;
         public boolean failToLoad = false;
+
+        public GifDecoder gifDecoder;
+        public Future<GifDecoder> gifDecoderTask;
+        public ScreenNail currentGifFrame;
+        public long requestedGif = MediaObject.INVALID_DATA_VERSION;
     }
 
     private class SourceListener implements ContentListener {
diff --git a/src/com/android/gallery3d/app/PhotoPage.java b/src/com/android/gallery3d/app/PhotoPage.java
index deb3b85..7bda6dc 100644
--- a/src/com/android/gallery3d/app/PhotoPage.java
+++ b/src/com/android/gallery3d/app/PhotoPage.java
@@ -78,7 +78,6 @@
 import com.android.gallery3d.ui.SynchronizedHandler;
 import com.android.gallery3d.util.GalleryUtils;
 import com.android.gallery3d.util.UsageStatistics;
-import com.android.gallery3d.util.ViewGifImage;
 
 import java.util.ArrayList;
 import java.util.Locale;
@@ -1275,10 +1274,6 @@
             // item is not ready or it is camera preview, ignore
             return;
         }
-        if (item.getMimeType().equals(MediaItem.MIME_TYPE_GIF)) {
-            viewAnimateGif((Activity) mActivity, item.getContentUri());
-            return;
-        }
 
         int supported = item.getSupportedOperations();
         boolean playVideo = ((supported & MediaItem.SUPPORT_PLAY) != 0);
@@ -1738,12 +1733,4 @@
         }
     }
 
-    private static void viewAnimateGif(Activity activity, Uri uri) {
-        Intent intent = new Intent(ViewGifImage.VIEW_GIF_ACTION, uri);
-        String scheme = uri.getScheme();
-        if ("file".equals(scheme)) {
-            intent.setDataAndType(uri, "image/gif");
-        }
-        activity.startActivity(intent);
-    }
 }
diff --git a/src/com/android/gallery3d/ui/PhotoView.java b/src/com/android/gallery3d/ui/PhotoView.java
index 6e1c662..2282290 100644
--- a/src/com/android/gallery3d/ui/PhotoView.java
+++ b/src/com/android/gallery3d/ui/PhotoView.java
@@ -736,7 +736,7 @@
             canvas.translate((int) (cx + 0.5f), (int) (cy + 0.5f));
             int s = (int) (scale * Math.min(r.width(), r.height()) + 0.5f);
             //Full pic locates at index 0 of the array in PhotoDataAdapter
-            if (mModel.isVideo(0) || mModel.isGif(0)) {
+            if (mModel.isVideo(0)) {
                 drawVideoPlayIcon(canvas, s);
             }
             if (mLoadingState == Model.LOADING_FAIL ) {
@@ -866,7 +866,7 @@
                 invalidate();
             }
             int s = Math.min(drawW, drawH);
-            if (mModel.isVideo(mIndex) || mModel.isGif(mIndex)) {
+            if (mModel.isVideo(mIndex)) {
                 drawVideoPlayIcon(canvas, s);
             }
 
diff --git a/src/com/android/gallery3d/util/GIFView.java b/src/com/android/gallery3d/util/GIFView.java
deleted file mode 100644
index 6c6c49b..0000000
--- a/src/com/android/gallery3d/util/GIFView.java
+++ /dev/null
@@ -1,234 +0,0 @@
-package com.android.gallery3d.util;
-
-import com.android.gallery3d.R;
-
-import android.content.Context;
-import android.content.ContentResolver;
-import android.content.res.AssetManager;
-import android.database.Cursor;
-//import android.drm.DrmHelper;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-import android.widget.ImageView;
-import android.widget.Toast;
-
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.io.IOException;
-
-public class GIFView extends ImageView implements GifAction {
-
-    private static final String  TAG            = "GIFView";
-    private static final float   SCALE_LIMIT    = 4;
-    private static final long    FRAME_DELAY    = 200; //milliseconds
-
-    private GifDecoder           mGifDecoder    = null;
-    private Bitmap               mCurrentImage  = null;
-    private DrawThread           mDrawThread    = null;
-
-    private Uri                  mUri;
-    private Context              mContext;
-
-    public GIFView(Context context) {
-        super(context);
-        mContext = context;
-    }
-
-    public boolean setDrawable(Uri uri) {
-        if (null == uri) {
-            return false;
-        }
-        mUri = uri;
-
-        // Let decode the GIF image from byte stream instead of file stream
-//        String filepath = DrmHelper.getFilePath(mContext, mUri);
-//        if (DrmHelper.isDrmFile(filepath)) {
-//            byte[] bytes = DrmHelper.getDrmImageBytes(filepath);
-//            DrmHelper.manageDrmLicense(mContext, this.getHandler(), filepath,
-//                    "image/gif");
-//            if (bytes == null) {
-//                return false;
-//            }
-//            startDecode(bytes);
-//            return true;
-//        }
-
-        InputStream is = getInputStream(uri);
-        if (is == null || (getFileSize (is) == 0)) {
-            return false;
-        }
-        startDecode(is);
-        return true;
-    }
-
-    private int getFileSize (InputStream is) {
-        if(is == null) return 0;
-
-        int size = 0;
-        try {
-            if (is instanceof FileInputStream) {
-                FileInputStream f = (FileInputStream) is;
-                size = (int) f.getChannel().size();
-            } else {
-                while (-1 != is.read()) {
-                    size++;
-                }
-            }
-
-        } catch (IOException e) {
-            Log.e(TAG, "catch exception:" + e);
-        }
-
-        return size;
-
-    }
-
-    private InputStream getInputStream (Uri uri) {
-        ContentResolver cr = mContext.getContentResolver();
-        InputStream input = null;
-        try {
-            input = cr.openInputStream(uri);
-        } catch (IOException e) {
-            Log.e(TAG, "catch exception:" + e);
-        }
-        return input;
-    }
-
-    private void startDecode(InputStream is) {
-        freeGifDecoder();
-        mGifDecoder = new GifDecoder(is, this);
-        mGifDecoder.start();
-    }
-
-    private void startDecode(byte[] bytes) {
-        freeGifDecoder();
-        mGifDecoder = new GifDecoder(bytes, this);
-        mGifDecoder.start();
-    }
-
-    protected void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-        if (mGifDecoder == null) {
-            return;
-        }
-
-        if (mCurrentImage == null) {
-            mCurrentImage = mGifDecoder.getImage();
-        }
-        if (mCurrentImage == null) {
-            // if this gif can not be displayed, just try to show it as jpg by parsing mUri
-            setImageURI(mUri);
-            return;
-        }
-        setImageURI(null);
-        int saveCount = canvas.getSaveCount();
-        canvas.save();
-        canvas.translate(getPaddingLeft(), getPaddingTop());
-        Rect sRect = null;
-        Rect dRect = null;
-
-        int imageHeight = mCurrentImage.getHeight();
-        int imageWidth = mCurrentImage.getWidth();
-
-        int displayHeight = ViewGifImage.mDM.heightPixels;
-        int displayWidth = ViewGifImage.mDM.widthPixels;
-
-        int width, height;
-        if (imageWidth >= displayWidth || imageHeight >= displayHeight) {
-            // scale-down the image
-            if (imageWidth * displayHeight > displayWidth * imageHeight) {
-                width = displayWidth;
-                height = (imageHeight * width) / imageWidth;
-            } else {
-                height = displayHeight;
-                width = (imageWidth * height) / imageHeight;
-            }
-        } else {
-            // scale-up the image
-            float scale = Math.min(SCALE_LIMIT, Math.min(displayWidth / (float) imageWidth,
-                    displayHeight / (float) imageHeight));
-            width = (int) (imageWidth * scale);
-            height = (int) (imageHeight * scale);
-        }
-        dRect = new Rect((displayWidth - width) / 2, (displayHeight - height) / 2,
-                (displayWidth + width) / 2, (displayHeight + height) / 2);
-        canvas.drawBitmap(mCurrentImage, sRect, dRect, null);
-        canvas.restoreToCount(saveCount);
-    }
-
-    public void parseOk(boolean parseStatus, int frameIndex) {
-        if (parseStatus) {
-            //indicates the start of a new GIF
-            if (mGifDecoder != null && frameIndex == -1
-                    && mGifDecoder.getFrameCount() > 1) {
-                if (mDrawThread != null) {
-                    mDrawThread = null;
-                }
-                mDrawThread = new DrawThread();
-                mDrawThread.start();
-            }
-        } else {
-            Log.e(TAG, "parse error");
-        }
-    }
-
-    private Handler mRedrawHandler = new Handler() {
-        public void handleMessage(Message msg) {
-            invalidate();
-        }
-    };
-
-    private class DrawThread extends Thread {
-        public void run() {
-            if (mGifDecoder == null) {
-                return;
-            }
-
-            while (true) {
-                if (!isShown() || mRedrawHandler == null) {
-                    break;
-                }
-                if (mGifDecoder == null) {
-                    return;
-                }
-                GifFrame frame = mGifDecoder.next();
-                mCurrentImage = frame.mImage;
-
-                Message msg = mRedrawHandler.obtainMessage();
-                mRedrawHandler.sendMessage(msg);
-                try {
-                    Thread.sleep(getDelay(frame));
-                } catch (InterruptedException e) {
-                    Log.e(TAG, "catch exception:" + e);
-                }
-            }
-        }
-
-    }
-
-    private long getDelay (GifFrame frame) {
-        //in milliseconds
-        return frame.mDelayInMs == 0 ? FRAME_DELAY : frame.mDelayInMs;
-    }
-
-    private void freeGifDecoder () {
-        if (mGifDecoder != null) {
-            mGifDecoder.free();
-            mGifDecoder = null;
-        }
-
-    }
-
-    public void freeMemory() {
-        if (mDrawThread != null) {
-            mDrawThread = null;
-        }
-        freeGifDecoder();
-    }
-}
diff --git a/src/com/android/gallery3d/util/GifDecoder.java b/src/com/android/gallery3d/util/GifDecoder.java
index 4235fc5..1342355 100755
--- a/src/com/android/gallery3d/util/GifDecoder.java
+++ b/src/com/android/gallery3d/util/GifDecoder.java
@@ -7,7 +7,7 @@
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 
-public class GifDecoder extends Thread {
+public class GifDecoder {
 
     public static final int STATUS_PARSING = 0;
     public static final int STATUS_FORMAT_ERROR = 1;
@@ -71,14 +71,16 @@
     public GifDecoder(byte[] data, GifAction act) {
         mGifData = data;
         mGifAction = act;
+        startDecoder();
     }
 
     public GifDecoder(InputStream is, GifAction act) {
         mIS = is;
         mGifAction = act;
+        startDecoder();
     }
 
-    public void run() {
+    public void startDecoder() {
         if (mIS != null) {
             readStream();
         } else if (mGifData != null) {
@@ -295,10 +297,14 @@
                 readContents();
                 if (mFrameCount < 0) {
                     mStatus = STATUS_FORMAT_ERROR;
-                    mGifAction.parseOk(false, -1);
+                    if (mGifAction != null) {
+                        mGifAction.parseOk(false, -1);
+                    }
                 } else {
                     mStatus = STATUS_FINISH;
-                    mGifAction.parseOk(true, -1);
+                    if (mGifAction != null) {
+                        mGifAction.parseOk(true, -1);
+                    }
                 }
             }
             try {
@@ -308,7 +314,9 @@
             }
         } else {
             mStatus = STATUS_OPEN_ERROR;
-            mGifAction.parseOk(false, -1);
+            if (mGifAction != null) {
+                mGifAction.parseOk(false, -1);
+            }
         }
         return mStatus;
     }
@@ -624,7 +632,9 @@
                 mAct[mTransIndex] = save;
             }
             resetFrame();
-            mGifAction.parseOk(true, mFrameCount);
+            if (mGifAction != null) {
+                mGifAction.parseOk(true, mFrameCount);
+            }
         } catch (OutOfMemoryError e) {
             Log.e("GifDecoder", ">>> log  : " + e.toString());
             e.printStackTrace();
diff --git a/src/com/android/gallery3d/util/GifRequest.java b/src/com/android/gallery3d/util/GifRequest.java
new file mode 100755
index 0000000..ea928ad
--- /dev/null
+++ b/src/com/android/gallery3d/util/GifRequest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux Foundation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.gallery3d.util;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.util.Log;
+
+import com.android.gallery3d.util.ThreadPool.Job;
+import com.android.gallery3d.util.ThreadPool.JobContext;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+
+public class GifRequest implements Job<GifDecoder> {
+
+    private static final String TAG = "GifRequest";
+    Uri itemUri;
+    Context mContext;
+
+    public GifRequest(Uri uri, Context context) {
+        itemUri = uri;
+        mContext = context;
+    }
+
+    private InputStream getInputStream(Uri uri) {
+        ContentResolver cr = mContext.getContentResolver();
+        InputStream input = null;
+        try {
+            input = cr.openInputStream(uri);
+        } catch (IOException e) {
+            Log.e(TAG, "catch exception:" + e);
+        }
+        return input;
+    }
+
+    @Override
+    public GifDecoder run(JobContext jc) {
+        InputStream input = getInputStream(itemUri);
+        if (input != null) {
+            return new GifDecoder(getInputStream(itemUri), null);
+        } else {
+            return null;
+        }
+    }
+
+
+}
+
diff --git a/src/com/android/gallery3d/util/ViewGifImage.java b/src/com/android/gallery3d/util/ViewGifImage.java
deleted file mode 100755
index cdd5092..0000000
--- a/src/com/android/gallery3d/util/ViewGifImage.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package com.android.gallery3d.util;
-
-import com.android.gallery3d.R;
-
-import android.app.Activity;
-import android.content.res.Configuration;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.util.DisplayMetrics;
-import android.view.ViewGroup.LayoutParams;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-public class ViewGifImage extends Activity {
-    private static final String TAG       = "ViewGifImage";
-    public  static final String VIEW_GIF_ACTION = "com.android.gallery3d.VIEW_GIF";
-
-    public  static DisplayMetrics mDM;
-
-    private ImageView mGifView;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.view_gif_image);
-        mDM = new DisplayMetrics();
-        getWindowManager().getDefaultDisplay().getMetrics(mDM);
-        if (getIntent().getAction() != null
-                && getIntent().getAction().equals(VIEW_GIF_ACTION)) {
-            Uri gifUri = getIntent().getData();
-            showGifPicture(gifUri);
-        }
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-        finish();
-    }
-
-    @Override
-    protected void onDestroy() {
-        if (mGifView != null && mGifView instanceof GIFView) {
-            ((GIFView) mGifView).freeMemory();
-            mGifView = null;
-        }
-        super.onDestroy();
-    }
-
-    private void showGifPicture(Uri uri) {
-        mGifView = new GIFView(this);
-        ((LinearLayout) findViewById(R.id.image_absoluteLayout)).addView(mGifView,
-                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
-        if (((GIFView) mGifView).setDrawable(uri)) return;
-        
-        finish();
-
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        getWindowManager().getDefaultDisplay().getMetrics(mDM);
-        super.onConfigurationChanged(newConfig);
-    }
-}