1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.camera.data;
18
19import android.content.ContentResolver;
20import android.content.Context;
21import android.graphics.Bitmap;
22import android.provider.MediaStore;
23import android.view.LayoutInflater;
24import android.view.View;
25import android.widget.ImageView;
26
27import com.android.camera.data.FilmstripItemAttributes.Attributes;
28import com.android.camera.debug.Log;
29import com.android.camera.util.Size;
30import com.android.camera2.R;
31import com.bumptech.glide.Glide;
32import com.google.common.base.Optional;
33
34import java.util.concurrent.TimeUnit;
35
36import javax.annotation.Nonnull;
37
38/**
39 * Backing data for a single video displayed in the filmstrip.
40 */
41public class VideoItem extends FilmstripItemBase<VideoItemData> {
42    private static class VideoViewHolder {
43        private final ImageView mVideoView;
44        private final ImageView mPlayButton;
45
46        public VideoViewHolder(ImageView videoView, ImageView playButton) {
47            mVideoView = videoView;
48            mPlayButton = playButton;
49        }
50    }
51
52    private static final Log.Tag TAG = new Log.Tag("VideoItem");
53
54    private static final FilmstripItemAttributes VIDEO_ITEM_ATTRIBUTES =
55          new FilmstripItemAttributes.Builder()
56                .with(Attributes.CAN_SHARE)
57                .with(Attributes.CAN_PLAY)
58                .with(Attributes.CAN_DELETE)
59                .with(Attributes.CAN_SWIPE_AWAY)
60                .with(Attributes.HAS_DETAILED_CAPTURE_INFO)
61                .with(Attributes.IS_VIDEO)
62                .build();
63
64    private final VideoItemFactory mVideoItemFactory;
65
66    private Size mCachedSize;
67
68    public VideoItem(Context context, GlideFilmstripManager manager, VideoItemData data,
69          VideoItemFactory videoItemFactory) {
70        super(context, manager, data, VIDEO_ITEM_ATTRIBUTES);
71        mVideoItemFactory = videoItemFactory;
72    }
73
74    /**
75     * We can't trust the media store and we can't afford the performance overhead of
76     * synchronously decoding the video header for every item when loading our data set
77     * from the media store, so we instead run the metadata loader in the background
78     * to decode the video header for each item and prefer whatever values it obtains.
79     */
80    private int getBestWidth() {
81        int metadataWidth = mMetaData.getVideoWidth();
82        if (metadataWidth > 0) {
83            return metadataWidth;
84        } else {
85            return mData.getDimensions().getWidth();
86        }
87    }
88
89    private int getBestHeight() {
90        int metadataHeight = mMetaData.getVideoHeight();
91        if (metadataHeight > 0) {
92            return metadataHeight;
93        } else {
94            return mData.getDimensions().getHeight();
95        }
96    }
97
98    /**
99     * If the metadata loader has determined from the video header that we need to rotate the video
100     * 90 or 270 degrees, then we swap the width and height.
101     */
102    public int getWidth() {
103        return mMetaData.isVideoRotated() ? getBestHeight() : getBestWidth();
104    }
105
106    public int getHeight() {
107        return mMetaData.isVideoRotated() ?  getBestWidth() : getBestHeight();
108    }
109
110    @Override
111    public Size getDimensions() {
112        int width = getWidth();
113        int height = getHeight();
114        if (mCachedSize == null ||
115                width != mCachedSize.getWidth() || height != mCachedSize.getHeight()) {
116            mCachedSize = new Size(width, height);
117        }
118        return mCachedSize;
119    }
120
121    @Override
122    public boolean delete() {
123        ContentResolver cr = mContext.getContentResolver();
124        cr.delete(VideoDataQuery.CONTENT_URI,
125              MediaStore.Video.VideoColumns._ID + "=" + mData.getContentId(), null);
126        return super.delete();
127    }
128
129    @Override
130    public Optional<MediaDetails> getMediaDetails() {
131        Optional<MediaDetails> optionalDetails = super.getMediaDetails();
132        if (optionalDetails.isPresent()) {
133            MediaDetails mediaDetails = optionalDetails.get();
134            String duration = MediaDetails.formatDuration(mContext,
135                    TimeUnit.MILLISECONDS.toSeconds(mData.getVideoDurationMillis()));
136            mediaDetails.addDetail(MediaDetails.INDEX_DURATION, duration);
137        }
138        return optionalDetails;
139    }
140
141    @Override
142    public FilmstripItem refresh() {
143        return mVideoItemFactory.get(mData.getUri());
144    }
145
146    @Override
147    public View getView(Optional<View> optionalView,
148          LocalFilmstripDataAdapter adapter, boolean isInProgress,
149          final VideoClickedCallback videoClickedCallback) {
150
151        View view;
152        VideoViewHolder viewHolder;
153
154        if (optionalView.isPresent()) {
155            view = optionalView.get();
156            viewHolder = getViewHolder(view);
157        } else {
158            view = LayoutInflater.from(mContext).inflate(R.layout.filmstrip_video, null);
159            view.setTag(R.id.mediadata_tag_viewtype, getItemViewType().ordinal());
160            ImageView videoView = (ImageView) view.findViewById(R.id.video_view);
161            ImageView playButton = (ImageView) view.findViewById(R.id.play_button);
162
163            viewHolder = new VideoViewHolder(videoView, playButton);
164            view.setTag(R.id.mediadata_tag_target, viewHolder);
165        }
166
167        if (viewHolder != null) {
168            // ImageView for the play icon.
169            viewHolder.mPlayButton.setOnClickListener(new View.OnClickListener() {
170                @Override
171                public void onClick(View v) {
172                    videoClickedCallback.playVideo(mData.getUri(), mData.getTitle());
173                }
174            });
175
176            view.setContentDescription(mContext.getResources().getString(
177                  R.string.video_date_content_description,
178                  mDateFormatter.format(mData.getLastModifiedDate())));
179
180            renderTiny(viewHolder);
181        } else {
182            Log.w(TAG, "getView called with a view that is not compatible with VideoItem.");
183        }
184
185        return view;
186    }
187
188    @Override
189    public void renderTiny(@Nonnull View view) {
190        renderTiny(getViewHolder(view));
191    }
192
193    @Override
194    public void renderThumbnail(@Nonnull View view) {
195        mGlideManager.loadScreen(mData.getUri(), generateSignature(mData), mSuggestedSize)
196              .thumbnail(mGlideManager.loadMediaStoreThumb(mData.getUri(),
197                    generateSignature(mData)))
198              .into(getViewHolder(view).mVideoView);
199    }
200
201    @Override
202    public void renderFullRes(@Nonnull View view) { }
203
204    @Override
205    public void recycle(@Nonnull View view) {
206        VideoViewHolder holder = getViewHolder(view);
207        if (holder != null) {
208            Glide.clear(getViewHolder(view).mVideoView);
209        }
210    }
211
212    @Override
213    public FilmstripItemType getItemViewType() {
214        return FilmstripItemType.VIDEO;
215    }
216
217    @Override
218    public Optional<Bitmap> generateThumbnail(int boundingWidthPx, int boundingHeightPx) {
219        return Optional.fromNullable(FilmstripItemUtils.loadVideoThumbnail(
220                getData().getFilePath()));
221    }
222
223    @Override
224    public String toString() {
225        return "VideoItem: " + mData.toString();
226    }
227
228    private void renderTiny(@Nonnull VideoViewHolder viewHolder) {
229        mGlideManager.loadMediaStoreThumb(mData.getUri(), generateSignature(mData))
230              .into(viewHolder.mVideoView);
231    }
232
233    private VideoViewHolder getViewHolder(@Nonnull View view) {
234        Object container = view.getTag(R.id.mediadata_tag_target);
235        if (container instanceof VideoViewHolder) {
236            return (VideoViewHolder) container;
237        }
238
239        return null;
240    }
241}
242