/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.camera.data;

import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.CamcorderProfile;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;

import com.android.camera.Storage;
import com.android.camera.debug.Log;
import com.android.camera.util.CameraUtil;
import com.android.camera2.R;
import com.bumptech.glide.BitmapRequestBuilder;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.BitmapEncoder;

import java.io.File;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;

/**
 * A base class for all the local media files. The bitmap is loaded in
 * background thread. Subclasses should implement their own background loading
 * thread by sub-classing BitmapLoadTask and overriding doInBackground() to
 * return a bitmap.
 */
public abstract class LocalMediaData implements LocalData {
    /** The minimum id to use to query for all media at a given media store uri */
    static final int QUERY_ALL_MEDIA_ID = -1;
    private static final String CAMERA_PATH = Storage.DIRECTORY + "%";
    private static final String SELECT_BY_PATH = MediaStore.MediaColumns.DATA + " LIKE ?";
    private static final int MEDIASTORE_THUMB_WIDTH = 512;
    private static final int MEDIASTORE_THUMB_HEIGHT = 384;

    protected final long mContentId;
    protected final String mTitle;
    protected final String mMimeType;
    protected final long mDateTakenInMilliSeconds;
    protected final long mDateModifiedInSeconds;
    protected final String mPath;
    // width and height should be adjusted according to orientation.
    protected final int mWidth;
    protected final int mHeight;
    protected final long mSizeInBytes;
    protected final double mLatitude;
    protected final double mLongitude;
    protected final Bundle mMetaData;

    private static final int JPEG_COMPRESS_QUALITY = 90;
    private static final BitmapEncoder JPEG_ENCODER =
            new BitmapEncoder(Bitmap.CompressFormat.JPEG, JPEG_COMPRESS_QUALITY);

    /**
     * Used for thumbnail loading optimization. True if this data has a
     * corresponding visible view.
     */
    protected Boolean mUsing = false;

    public LocalMediaData(long contentId, String title, String mimeType,
            long dateTakenInMilliSeconds, long dateModifiedInSeconds, String path,
            int width, int height, long sizeInBytes, double latitude,
            double longitude) {
        mContentId = contentId;
        mTitle = title;
        mMimeType = mimeType;
        mDateTakenInMilliSeconds = dateTakenInMilliSeconds;
        mDateModifiedInSeconds = dateModifiedInSeconds;
        mPath = path;
        mWidth = width;
        mHeight = height;
        mSizeInBytes = sizeInBytes;
        mLatitude = latitude;
        mLongitude = longitude;
        mMetaData = new Bundle();
    }

    private interface CursorToLocalData {
        public LocalData build(Cursor cursor);
    }

    private static List<LocalData> queryLocalMediaData(ContentResolver contentResolver,
            Uri contentUri, String[] projection, long minimumId, String orderBy,
            CursorToLocalData builder) {
        String selection = SELECT_BY_PATH + " AND " + MediaStore.MediaColumns._ID + " > ?";
        String[] selectionArgs = new String[] { CAMERA_PATH, Long.toString(minimumId) };

        Cursor cursor = contentResolver.query(contentUri, projection,
                selection, selectionArgs, orderBy);
        List<LocalData> result = new ArrayList<LocalData>();
        if (cursor != null) {
            while (cursor.moveToNext()) {
                LocalData data = builder.build(cursor);
                if (data != null) {
                    result.add(data);
                } else {
                    final int dataIndex = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
                    Log.e(TAG, "Error loading data:" + cursor.getString(dataIndex));
                }
            }

            cursor.close();
        }
        return result;
    }

    @Override
    public long getDateTaken() {
        return mDateTakenInMilliSeconds;
    }

    @Override
    public long getDateModified() {
        return mDateModifiedInSeconds;
    }

    @Override
    public long getContentId() {
        return mContentId;
    }

    @Override
    public String getTitle() {
        return mTitle;
    }

    @Override
    public int getWidth() {
        return mWidth;
    }

    @Override
    public int getHeight() {
        return mHeight;
    }

    @Override
    public int getRotation() {
        return 0;
    }

    @Override
    public String getPath() {
        return mPath;
    }

    @Override
    public long getSizeInBytes() {
        return mSizeInBytes;
    }

    @Override
    public boolean isUIActionSupported(int action) {
        return false;
    }

    @Override
    public boolean isDataActionSupported(int action) {
        return false;
    }

    @Override
    public boolean delete(Context context) {
        File f = new File(mPath);
        return f.delete();
    }

    @Override
    public void onFullScreen(boolean fullScreen) {
        // do nothing.
    }

    @Override
    public boolean canSwipeInFullScreen() {
        return true;
    }

    protected ImageView fillImageView(Context context, ImageView v,
            int thumbWidth, int thumbHeight, int placeHolderResourceId,
            LocalDataAdapter adapter, boolean isInProgress) {
        Glide.with(context)
            .loadFromMediaStore(getUri(), mMimeType, mDateModifiedInSeconds, 0)
            .fitCenter()
            .placeholder(placeHolderResourceId)
            .into(v);

        v.setContentDescription(context.getResources().getString(
                R.string.media_date_content_description,
                getReadableDate(mDateModifiedInSeconds)));

        return v;
    }

    @Override
    public View getView(Context context, View recycled, int thumbWidth, int thumbHeight,
            int placeHolderResourceId, LocalDataAdapter adapter, boolean isInProgress) {
        final ImageView imageView;
        if (recycled != null) {
            imageView = (ImageView) recycled;
        } else {
            imageView = (ImageView) LayoutInflater.from(context)
                .inflate(R.layout.filmstrip_image, null);
            imageView.setTag(R.id.mediadata_tag_viewtype, getItemViewType().ordinal());
        }

        return fillImageView(context, imageView, thumbWidth, thumbHeight,
                placeHolderResourceId, adapter, isInProgress);
    }

    @Override
    public void loadFullImage(Context context, int thumbWidth, int thumbHeight, View view,
            LocalDataAdapter adapter) {
        // Default is do nothing.
        // Can be implemented by sub-classes.
    }

    @Override
    public void prepare() {
        synchronized (mUsing) {
            mUsing = true;
        }
    }

    @Override
    public void recycle(View view) {
        synchronized (mUsing) {
            mUsing = false;
        }
    }

    @Override
    public double[] getLatLong() {
        if (mLatitude == 0 && mLongitude == 0) {
            return null;
        }
        return new double[] {
                mLatitude, mLongitude
        };
    }

    protected boolean isUsing() {
        synchronized (mUsing) {
            return mUsing;
        }
    }

    @Override
    public String getMimeType() {
        return mMimeType;
    }

    @Override
    public MediaDetails getMediaDetails(Context context) {
        MediaDetails mediaDetails = new MediaDetails();
        mediaDetails.addDetail(MediaDetails.INDEX_TITLE, mTitle);
        mediaDetails.addDetail(MediaDetails.INDEX_WIDTH, mWidth);
        mediaDetails.addDetail(MediaDetails.INDEX_HEIGHT, mHeight);
        mediaDetails.addDetail(MediaDetails.INDEX_PATH, mPath);
        mediaDetails.addDetail(MediaDetails.INDEX_DATETIME,
                getReadableDate(mDateModifiedInSeconds));
        if (mSizeInBytes > 0) {
            mediaDetails.addDetail(MediaDetails.INDEX_SIZE, mSizeInBytes);
        }
        if (mLatitude != 0 && mLongitude != 0) {
            String locationString = String.format(Locale.getDefault(), "%f, %f", mLatitude,
                    mLongitude);
            mediaDetails.addDetail(MediaDetails.INDEX_LOCATION, locationString);
        }
        return mediaDetails;
    }

    private static String getReadableDate(long dateInSeconds) {
        DateFormat dateFormatter = DateFormat.getDateTimeInstance();
        return dateFormatter.format(new Date(dateInSeconds * 1000));
    }

    @Override
    public abstract int getViewType();

    @Override
    public Bundle getMetadata() {
        return mMetaData;
    }

    @Override
    public boolean isMetadataUpdated() {
        return MetadataLoader.isMetadataCached(this);
    }

    public static final class PhotoData extends LocalMediaData {
        private static final Log.Tag TAG = new Log.Tag("PhotoData");

        public static final int COL_ID = 0;
        public static final int COL_TITLE = 1;
        public static final int COL_MIME_TYPE = 2;
        public static final int COL_DATE_TAKEN = 3;
        public static final int COL_DATE_MODIFIED = 4;
        public static final int COL_DATA = 5;
        public static final int COL_ORIENTATION = 6;
        public static final int COL_WIDTH = 7;
        public static final int COL_HEIGHT = 8;
        public static final int COL_SIZE = 9;
        public static final int COL_LATITUDE = 10;
        public static final int COL_LONGITUDE = 11;

        // GL max texture size: keep bitmaps below this value.
        private static final int MAXIMUM_TEXTURE_SIZE = 2048;

        static final Uri CONTENT_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;

        private static final String QUERY_ORDER = MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC, "
                + MediaStore.Images.ImageColumns._ID + " DESC";
        /**
         * These values should be kept in sync with column IDs (COL_*) above.
         */
        private static final String[] QUERY_PROJECTION = {
                MediaStore.Images.ImageColumns._ID,           // 0, int
                MediaStore.Images.ImageColumns.TITLE,         // 1, string
                MediaStore.Images.ImageColumns.MIME_TYPE,     // 2, string
                MediaStore.Images.ImageColumns.DATE_TAKEN,    // 3, int
                MediaStore.Images.ImageColumns.DATE_MODIFIED, // 4, int
                MediaStore.Images.ImageColumns.DATA,          // 5, string
                MediaStore.Images.ImageColumns.ORIENTATION,   // 6, int, 0, 90, 180, 270
                MediaStore.Images.ImageColumns.WIDTH,         // 7, int
                MediaStore.Images.ImageColumns.HEIGHT,        // 8, int
                MediaStore.Images.ImageColumns.SIZE,          // 9, long
                MediaStore.Images.ImageColumns.LATITUDE,      // 10, double
                MediaStore.Images.ImageColumns.LONGITUDE      // 11, double
        };

        private static final int mSupportedUIActions = ACTION_DEMOTE | ACTION_PROMOTE | ACTION_ZOOM;
        private static final int mSupportedDataActions =
                DATA_ACTION_DELETE | DATA_ACTION_EDIT | DATA_ACTION_SHARE;

        /** from MediaStore, can only be 0, 90, 180, 270 */
        private final int mOrientation;
        /** @see #getSignature() */
        private final String mSignature;

        public static LocalData fromContentUri(ContentResolver cr, Uri contentUri) {
            List<LocalData> newPhotos = query(cr, contentUri, QUERY_ALL_MEDIA_ID);
            if (newPhotos.isEmpty()) {
                return null;
            }
            return newPhotos.get(0);
        }

        public PhotoData(long id, String title, String mimeType,
                long dateTakenInMilliSeconds, long dateModifiedInSeconds,
                String path, int orientation, int width, int height,
                long sizeInBytes, double latitude, double longitude) {
            super(id, title, mimeType, dateTakenInMilliSeconds, dateModifiedInSeconds,
                    path, width, height, sizeInBytes, latitude, longitude);
            mOrientation = orientation;
            mSignature = mimeType + orientation + dateModifiedInSeconds;
        }

        static List<LocalData> query(ContentResolver cr, Uri uri, long lastId) {
            return queryLocalMediaData(cr, uri, QUERY_PROJECTION, lastId, QUERY_ORDER,
                    new PhotoDataBuilder());
        }

        private static PhotoData buildFromCursor(Cursor c) {
            long id = c.getLong(COL_ID);
            String title = c.getString(COL_TITLE);
            String mimeType = c.getString(COL_MIME_TYPE);
            long dateTakenInMilliSeconds = c.getLong(COL_DATE_TAKEN);
            long dateModifiedInSeconds = c.getLong(COL_DATE_MODIFIED);
            String path = c.getString(COL_DATA);
            int orientation = c.getInt(COL_ORIENTATION);
            int width = c.getInt(COL_WIDTH);
            int height = c.getInt(COL_HEIGHT);
            if (width <= 0 || height <= 0) {
                Log.w(TAG, "Zero dimension in ContentResolver for "
                        + path + ":" + width + "x" + height);
                BitmapFactory.Options opts = new BitmapFactory.Options();
                opts.inJustDecodeBounds = true;
                BitmapFactory.decodeFile(path, opts);
                if (opts.outWidth > 0 && opts.outHeight > 0) {
                    width = opts.outWidth;
                    height = opts.outHeight;
                } else {
                    Log.w(TAG, "Dimension decode failed for " + path);
                    Bitmap b = BitmapFactory.decodeFile(path);
                    if (b == null) {
                        Log.w(TAG, "PhotoData skipped."
                                + " Decoding " + path + "failed.");
                        return null;
                    }
                    width = b.getWidth();
                    height = b.getHeight();
                    if (width == 0 || height == 0) {
                        Log.w(TAG, "PhotoData skipped. Bitmap size 0 for " + path);
                        return null;
                    }
                }
            }

            long sizeInBytes = c.getLong(COL_SIZE);
            double latitude = c.getDouble(COL_LATITUDE);
            double longitude = c.getDouble(COL_LONGITUDE);
            PhotoData result = new PhotoData(id, title, mimeType, dateTakenInMilliSeconds,
                    dateModifiedInSeconds, path, orientation, width, height,
                    sizeInBytes, latitude, longitude);
            return result;
        }

        @Override
        public int getRotation() {
            return mOrientation;
        }

        @Override
        public String toString() {
            return "Photo:" + ",data=" + mPath + ",mimeType=" + mMimeType
                    + "," + mWidth + "x" + mHeight + ",orientation=" + mOrientation
                    + ",date=" + new Date(mDateTakenInMilliSeconds);
        }

        @Override
        public int getViewType() {
            return VIEW_TYPE_REMOVABLE;
        }

        @Override
        public boolean isUIActionSupported(int action) {
            return ((action & mSupportedUIActions) == action);
        }

        @Override
        public boolean isDataActionSupported(int action) {
            return ((action & mSupportedDataActions) == action);
        }

        @Override
        public boolean delete(Context context) {
            ContentResolver cr = context.getContentResolver();
            cr.delete(CONTENT_URI, MediaStore.Images.ImageColumns._ID + "=" + mContentId, null);
            return super.delete(context);
        }

        @Override
        public Uri getUri() {
            Uri baseUri = CONTENT_URI;
            return baseUri.buildUpon().appendPath(String.valueOf(mContentId)).build();
        }

        @Override
        public MediaDetails getMediaDetails(Context context) {
            MediaDetails mediaDetails = super.getMediaDetails(context);
            MediaDetails.extractExifInfo(mediaDetails, mPath);
            mediaDetails.addDetail(MediaDetails.INDEX_ORIENTATION, mOrientation);
            return mediaDetails;
        }

        @Override
        public int getLocalDataType() {
            return LOCAL_IMAGE;
        }

        @Override
        public LocalData refresh(Context context) {
            PhotoData newData = null;
            Cursor c = context.getContentResolver().query(getUri(), QUERY_PROJECTION, null,
                    null, null);
            if (c != null) {
                if (c.moveToFirst()) {
                    newData = buildFromCursor(c);
                }
                c.close();
            }

            return newData;
        }

        @Override
        public String getSignature() {
            return mSignature;
        }

        @Override
        protected ImageView fillImageView(Context context, final ImageView v, final int thumbWidth,
                final int thumbHeight, int placeHolderResourceId, LocalDataAdapter adapter,
                boolean isInProgress) {
            loadImage(context, v, thumbWidth, thumbHeight, placeHolderResourceId, false);

            int stringId = R.string.photo_date_content_description;
            if (PanoramaMetadataLoader.isPanorama(this) ||
                PanoramaMetadataLoader.isPanorama360(this)) {
                stringId = R.string.panorama_date_content_description;
            } else if (PanoramaMetadataLoader.isPanoramaAndUseViewer(this)) {
                // assume it's a PhotoSphere
                stringId = R.string.photosphere_date_content_description;
            } else if (RgbzMetadataLoader.hasRGBZData(this)) {
                stringId = R.string.refocus_date_content_description;
            }

            v.setContentDescription(context.getResources().getString(
                    stringId,
                    getReadableDate(mDateModifiedInSeconds)));

            return v;
        }

        private void loadImage(Context context, ImageView imageView, int thumbWidth,
                int thumbHeight, int placeHolderResourceId, boolean full) {

            //TODO: Figure out why these can be <= 0.
            if (thumbWidth <= 0 || thumbHeight <=0) {
                return;
            }

            BitmapRequestBuilder<Uri, Bitmap> request = Glide.with(context)
                .loadFromMediaStore(getUri(), mMimeType, mDateModifiedInSeconds, mOrientation)
                .asBitmap()
                .encoder(JPEG_ENCODER)
                .placeholder(placeHolderResourceId)
                .fitCenter();
            if (full) {
                request.thumbnail(Glide.with(context)
                        .loadFromMediaStore(getUri(), mMimeType, mDateModifiedInSeconds,
                            mOrientation)
                        .asBitmap()
                        .encoder(JPEG_ENCODER)
                        .override(thumbWidth, thumbHeight)
                        .fitCenter())
                    .override(Math.min(getWidth(), MAXIMUM_TEXTURE_SIZE),
                        Math.min(getHeight(), MAXIMUM_TEXTURE_SIZE));
            } else {
                request.thumbnail(Glide.with(context)
                        .loadFromMediaStore(getUri(), mMimeType, mDateModifiedInSeconds,
                            mOrientation)
                        .asBitmap()
                        .encoder(JPEG_ENCODER)
                        .override(MEDIASTORE_THUMB_WIDTH, MEDIASTORE_THUMB_HEIGHT))
                    .override(thumbWidth, thumbHeight);
            }
            request.into(imageView);
        }

        @Override
        public void recycle(View view) {
            super.recycle(view);
            if (view != null) {
                Glide.clear(view);
            }
        }

        @Override
        public LocalDataViewType getItemViewType() {
            return LocalDataViewType.PHOTO;
        }

        @Override
        public void loadFullImage(Context context, int thumbWidth, int thumbHeight, View v,
            LocalDataAdapter adapter)
        {
            loadImage(context, (ImageView) v, thumbWidth, thumbHeight, 0, true);
        }

        private static class PhotoDataBuilder implements CursorToLocalData {
            @Override
            public PhotoData build(Cursor cursor) {
                return LocalMediaData.PhotoData.buildFromCursor(cursor);
            }
        }
    }

    public static final class VideoData extends LocalMediaData {
        public static final int COL_ID = 0;
        public static final int COL_TITLE = 1;
        public static final int COL_MIME_TYPE = 2;
        public static final int COL_DATE_TAKEN = 3;
        public static final int COL_DATE_MODIFIED = 4;
        public static final int COL_DATA = 5;
        public static final int COL_WIDTH = 6;
        public static final int COL_HEIGHT = 7;
        public static final int COL_SIZE = 8;
        public static final int COL_LATITUDE = 9;
        public static final int COL_LONGITUDE = 10;
        public static final int COL_DURATION = 11;

        static final Uri CONTENT_URI = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;

        private static final int mSupportedUIActions = ACTION_DEMOTE | ACTION_PROMOTE;
        private static final int mSupportedDataActions =
                DATA_ACTION_DELETE | DATA_ACTION_PLAY | DATA_ACTION_SHARE;

        private static final String QUERY_ORDER = MediaStore.Video.VideoColumns.DATE_TAKEN
                + " DESC, " + MediaStore.Video.VideoColumns._ID + " DESC";
        /**
         * These values should be kept in sync with column IDs (COL_*) above.
         */
        private static final String[] QUERY_PROJECTION = {
                MediaStore.Video.VideoColumns._ID,           // 0, int
                MediaStore.Video.VideoColumns.TITLE,         // 1, string
                MediaStore.Video.VideoColumns.MIME_TYPE,     // 2, string
                MediaStore.Video.VideoColumns.DATE_TAKEN,    // 3, int
                MediaStore.Video.VideoColumns.DATE_MODIFIED, // 4, int
                MediaStore.Video.VideoColumns.DATA,          // 5, string
                MediaStore.Video.VideoColumns.WIDTH,         // 6, int
                MediaStore.Video.VideoColumns.HEIGHT,        // 7, int
                MediaStore.Video.VideoColumns.SIZE,          // 8 long
                MediaStore.Video.VideoColumns.LATITUDE,      // 9 double
                MediaStore.Video.VideoColumns.LONGITUDE,     // 10 double
                MediaStore.Video.VideoColumns.DURATION       // 11 long
        };

        /** The duration in milliseconds. */
        private final long mDurationInSeconds;
        private final String mSignature;

        public VideoData(long id, String title, String mimeType,
                long dateTakenInMilliSeconds, long dateModifiedInSeconds,
                String path, int width, int height, long sizeInBytes,
                double latitude, double longitude, long durationInSeconds) {
            super(id, title, mimeType, dateTakenInMilliSeconds, dateModifiedInSeconds,
                    path, width, height, sizeInBytes, latitude, longitude);
            mDurationInSeconds = durationInSeconds;
            mSignature = mimeType + dateModifiedInSeconds;
        }

        public static LocalData fromContentUri(ContentResolver cr, Uri contentUri) {
            List<LocalData> newVideos = query(cr, contentUri, QUERY_ALL_MEDIA_ID);
            if (newVideos.isEmpty()) {
                return null;
            }
            return newVideos.get(0);
        }

        static List<LocalData> query(ContentResolver cr, Uri uri, long lastId) {
            return queryLocalMediaData(cr, uri, QUERY_PROJECTION, lastId, QUERY_ORDER,
                    new VideoDataBuilder());
        }

        /**
         * We can't trust the media store and we can't afford the performance overhead of
         * synchronously decoding the video header for every item when loading our data set
         * from the media store, so we instead run the metadata loader in the background
         * to decode the video header for each item and prefer whatever values it obtains.
         */
        private int getBestWidth() {
            int metadataWidth = VideoRotationMetadataLoader.getWidth(this);
            if (metadataWidth > 0) {
                return metadataWidth;
            } else {
                return mWidth;
            }
        }

        private int getBestHeight() {
            int metadataHeight = VideoRotationMetadataLoader.getHeight(this);
            if (metadataHeight > 0) {
                return metadataHeight;
            } else {
                return mHeight;
            }
        }

        /**
         * If the metadata loader has determined from the video header that we need to rotate the video
         * 90 or 270 degrees, then we swap the width and height.
         */
        @Override
        public int getWidth() {
            return VideoRotationMetadataLoader.isRotated(this) ? getBestHeight() : getBestWidth();
        }

        @Override
        public int getHeight() {
            return VideoRotationMetadataLoader.isRotated(this) ?  getBestWidth() : getBestHeight();
        }

        private static VideoData buildFromCursor(Cursor c) {
            long id = c.getLong(COL_ID);
            String title = c.getString(COL_TITLE);
            String mimeType = c.getString(COL_MIME_TYPE);
            long dateTakenInMilliSeconds = c.getLong(COL_DATE_TAKEN);
            long dateModifiedInSeconds = c.getLong(COL_DATE_MODIFIED);
            String path = c.getString(COL_DATA);
            int width = c.getInt(COL_WIDTH);
            int height = c.getInt(COL_HEIGHT);

            // If the media store doesn't contain a width and a height, use the width and height
            // of the default camera mode instead. When the metadata loader runs, it will set the
            // correct values.
            if (width == 0 || height == 0) {
                Log.w(TAG, "failed to retrieve width and height from the media store, defaulting " +
                        " to camera profile");
                CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
                width = profile.videoFrameWidth;
                height = profile.videoFrameHeight;
            }

            long sizeInBytes = c.getLong(COL_SIZE);
            double latitude = c.getDouble(COL_LATITUDE);
            double longitude = c.getDouble(COL_LONGITUDE);
            long durationInSeconds = c.getLong(COL_DURATION) / 1000;
            VideoData d = new VideoData(id, title, mimeType, dateTakenInMilliSeconds,
                    dateModifiedInSeconds, path, width, height, sizeInBytes,
                    latitude, longitude, durationInSeconds);
            return d;
        }

        @Override
        public String toString() {
            return "Video:" + ",data=" + mPath + ",mimeType=" + mMimeType
                    + "," + mWidth + "x" + mHeight + ",date=" + new Date(mDateTakenInMilliSeconds);
        }

        @Override
        public int getViewType() {
            return VIEW_TYPE_REMOVABLE;
        }

        @Override
        public boolean isUIActionSupported(int action) {
            return ((action & mSupportedUIActions) == action);
        }

        @Override
        public boolean isDataActionSupported(int action) {
            return ((action & mSupportedDataActions) == action);
        }

        @Override
        public boolean delete(Context context) {
            ContentResolver cr = context.getContentResolver();
            cr.delete(CONTENT_URI, MediaStore.Video.VideoColumns._ID + "=" + mContentId, null);
            return super.delete(context);
        }

        @Override
        public Uri getUri() {
            Uri baseUri = CONTENT_URI;
            return baseUri.buildUpon().appendPath(String.valueOf(mContentId)).build();
        }

        @Override
        public MediaDetails getMediaDetails(Context context) {
            MediaDetails mediaDetails = super.getMediaDetails(context);
            String duration = MediaDetails.formatDuration(context, mDurationInSeconds);
            mediaDetails.addDetail(MediaDetails.INDEX_DURATION, duration);
            return mediaDetails;
        }

        @Override
        public int getLocalDataType() {
            return LOCAL_VIDEO;
        }

        @Override
        public LocalData refresh(Context context) {
            Cursor c = context.getContentResolver().query(getUri(), QUERY_PROJECTION, null,
                    null, null);
            if (c == null || !c.moveToFirst()) {
                return null;
            }
            VideoData newData = buildFromCursor(c);
            return newData;
        }

        @Override
        public String getSignature() {
            return mSignature;
        }

        @Override
        protected ImageView fillImageView(Context context, final ImageView v, final int thumbWidth,
                final int thumbHeight, int placeHolderResourceId, LocalDataAdapter adapter,
                boolean isInProgress) {

            //TODO: Figure out why these can be <= 0.
            if (thumbWidth <= 0 || thumbHeight <=0) {
                return v;
            }

            Glide.with(context)
                .loadFromMediaStore(getUri(), mMimeType, mDateModifiedInSeconds, 0)
                .asBitmap()
                .encoder(JPEG_ENCODER)
                .thumbnail(Glide.with(context)
                    .loadFromMediaStore(getUri(), mMimeType, mDateModifiedInSeconds, 0)
                    .asBitmap()
                    .encoder(JPEG_ENCODER)
                    .override(MEDIASTORE_THUMB_WIDTH, MEDIASTORE_THUMB_HEIGHT))
                .placeholder(placeHolderResourceId)
                .fitCenter()
                .override(thumbWidth, thumbHeight)
                .into(v);

            // Content descriptions applied to parent FrameView
            // see getView

            return v;
        }

        @Override
        public View getView(final Context context, View recycled,
                int thumbWidth, int thumbHeight, int placeHolderResourceId,
                LocalDataAdapter adapter, boolean isInProgress) {

            final VideoViewHolder viewHolder;
            final View result;
            if (recycled != null) {
                result = recycled;
                viewHolder = (VideoViewHolder) recycled.getTag(R.id.mediadata_tag_target);
            } else {
                result = LayoutInflater.from(context).inflate(R.layout.filmstrip_video, null);
                result.setTag(R.id.mediadata_tag_viewtype, getItemViewType().ordinal());
                ImageView videoView = (ImageView) result.findViewById(R.id.video_view);
                ImageView playButton = (ImageView) result.findViewById(R.id.play_button);
                viewHolder = new VideoViewHolder(videoView, playButton);
                result.setTag(R.id.mediadata_tag_target, viewHolder);
            }

            fillImageView(context, viewHolder.mVideoView, thumbWidth, thumbHeight,
                    placeHolderResourceId, adapter, isInProgress);

            // ImageView for the play icon.
            viewHolder.mPlayButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // TODO: refactor this into activities to avoid this class
                    // conversion.
                    CameraUtil.playVideo((Activity) context, getUri(), mTitle);
                }
            });

            result.setContentDescription(context.getResources().getString(
                    R.string.video_date_content_description,
                    getReadableDate(mDateModifiedInSeconds)));

            return result;
        }

        @Override
        public void recycle(View view) {
            super.recycle(view);
            VideoViewHolder videoViewHolder =
                    (VideoViewHolder) view.getTag(R.id.mediadata_tag_target);
            Glide.clear(videoViewHolder.mVideoView);
        }

        @Override
        public LocalDataViewType getItemViewType() {
            return LocalDataViewType.VIDEO;
        }
    }

    private static class VideoDataBuilder implements CursorToLocalData {

        @Override
        public VideoData build(Cursor cursor) {
            return LocalMediaData.VideoData.buildFromCursor(cursor);
        }
    }

     private static class VideoViewHolder {
        private final ImageView mVideoView;
        private final ImageView mPlayButton;

        public VideoViewHolder(ImageView videoView, ImageView playButton) {
            mVideoView = videoView;
            mPlayButton = playButton;
        }
    }
}
