VideoItem.java revision b6a8810b7a5ac0df1e19efd26628c01bcb32b97b
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 javax.annotation.Nonnull;
35
36/**
37 * Backing data for a single video displayed in the filmstrip.
38 */
39public class VideoItem extends FilmstripItemBase<VideoItemData> {
40    private static class VideoViewHolder {
41        private final ImageView mVideoView;
42        private final ImageView mPlayButton;
43
44        public VideoViewHolder(ImageView videoView, ImageView playButton) {
45            mVideoView = videoView;
46            mPlayButton = playButton;
47        }
48    }
49
50    private static final Log.Tag TAG = new Log.Tag("VideoItem");
51
52    private static final FilmstripItemAttributes VIDEO_ITEM_ATTRIBUTES =
53          new FilmstripItemAttributes.Builder()
54                .with(Attributes.CAN_SHARE)
55                .with(Attributes.CAN_PLAY)
56                .with(Attributes.CAN_DELETE)
57                .with(Attributes.CAN_SWIPE_AWAY)
58                .with(Attributes.HAS_DETAILED_CAPTURE_INFO)
59                .with(Attributes.IS_VIDEO)
60                .build();
61
62    private final VideoItemFactory mVideoItemFactory;
63
64    private Size mCachedSize;
65
66    public VideoItem(Context context, GlideFilmstripManager manager, VideoItemData data,
67          VideoItemFactory videoItemFactory) {
68        super(context, manager, data, VIDEO_ITEM_ATTRIBUTES);
69        mVideoItemFactory = videoItemFactory;
70    }
71
72    /**
73     * We can't trust the media store and we can't afford the performance overhead of
74     * synchronously decoding the video header for every item when loading our data set
75     * from the media store, so we instead run the metadata loader in the background
76     * to decode the video header for each item and prefer whatever values it obtains.
77     */
78    private int getBestWidth() {
79        int metadataWidth = mMetaData.getVideoWidth();
80        if (metadataWidth > 0) {
81            return metadataWidth;
82        } else {
83            return mData.getDimensions().getWidth();
84        }
85    }
86
87    private int getBestHeight() {
88        int metadataHeight = mMetaData.getVideoHeight();
89        if (metadataHeight > 0) {
90            return metadataHeight;
91        } else {
92            return mData.getDimensions().getHeight();
93        }
94    }
95
96    /**
97     * If the metadata loader has determined from the video header that we need to rotate the video
98     * 90 or 270 degrees, then we swap the width and height.
99     */
100    public int getWidth() {
101        return mMetaData.isVideoRotated() ? getBestHeight() : getBestWidth();
102    }
103
104    public int getHeight() {
105        return mMetaData.isVideoRotated() ?  getBestWidth() : getBestHeight();
106    }
107
108    @Override
109    public Size getDimensions() {
110        int width = getWidth();
111        int height = getHeight();
112        if (mCachedSize == null ||
113                width != mCachedSize.getWidth() || height != mCachedSize.getHeight()) {
114            mCachedSize = new Size(width, height);
115        }
116        return mCachedSize;
117    }
118
119    @Override
120    public boolean delete() {
121        ContentResolver cr = mContext.getContentResolver();
122        cr.delete(VideoDataQuery.CONTENT_URI,
123              MediaStore.Video.VideoColumns._ID + "=" + mData.getContentId(), null);
124        return super.delete();
125    }
126
127    @Override
128    public Optional<MediaDetails> getMediaDetails() {
129        Optional<MediaDetails> optionalDetails = super.getMediaDetails();
130        if (optionalDetails.isPresent()) {
131            MediaDetails mediaDetails = optionalDetails.get();
132            String duration = MediaDetails.formatDuration(mContext, mData.getVideoDurationMillis());
133            mediaDetails.addDetail(MediaDetails.INDEX_DURATION, duration);
134        }
135        return optionalDetails;
136    }
137
138    @Override
139    public FilmstripItem refresh() {
140        return mVideoItemFactory.get(mData.getUri());
141    }
142
143    @Override
144    public View getView(Optional<View> optionalView,
145          LocalFilmstripDataAdapter adapter, boolean isInProgress,
146          final VideoClickedCallback videoClickedCallback) {
147
148        View view;
149        VideoViewHolder viewHolder;
150
151        if (optionalView.isPresent()) {
152            view = optionalView.get();
153            viewHolder = getViewHolder(view);
154        } else {
155            view = LayoutInflater.from(mContext).inflate(R.layout.filmstrip_video, null);
156            view.setTag(R.id.mediadata_tag_viewtype, getItemViewType().ordinal());
157            ImageView videoView = (ImageView) view.findViewById(R.id.video_view);
158            ImageView playButton = (ImageView) view.findViewById(R.id.play_button);
159
160            viewHolder = new VideoViewHolder(videoView, playButton);
161            view.setTag(R.id.mediadata_tag_target, viewHolder);
162        }
163
164        if (viewHolder != null) {
165            // ImageView for the play icon.
166            viewHolder.mPlayButton.setOnClickListener(new View.OnClickListener() {
167                @Override
168                public void onClick(View v) {
169                    videoClickedCallback.playVideo(mData.getUri(), mData.getTitle());
170                }
171            });
172
173            view.setContentDescription(mContext.getResources().getString(
174                  R.string.video_date_content_description,
175                  mDateFormatter.format(mData.getLastModifiedDate())));
176
177            renderTiny(viewHolder);
178        } else {
179            Log.w(TAG, "getView called with a view that is not compatible with VideoItem.");
180        }
181
182        return view;
183    }
184
185    @Override
186    public void renderTiny(@Nonnull View view) {
187        renderTiny(getViewHolder(view));
188    }
189
190    @Override
191    public void renderThumbnail(@Nonnull View view) {
192        mGlideManager.loadScreen(mData.getUri(), generateSignature(mData),
193              mSuggestedWidthPx, mSuggestedHeightPx)
194              .thumbnail(mGlideManager.loadMediaStoreThumb(mData.getUri(),
195                    generateSignature(mData)))
196              .into(getViewHolder(view).mVideoView);
197    }
198
199    @Override
200    public void renderFullRes(@Nonnull View view) { }
201
202    @Override
203    public void recycle(@Nonnull View view) {
204        VideoViewHolder holder = getViewHolder(view);
205        if (holder != null) {
206            Glide.clear(getViewHolder(view).mVideoView);
207        }
208    }
209
210    @Override
211    public FilmstripItemType getItemViewType() {
212        return FilmstripItemType.VIDEO;
213    }
214
215    @Override
216    public Optional<Bitmap> generateThumbnail(int boundingWidthPx, int boundingHeightPx) {
217        return Optional.of(FilmstripItemUtils.loadVideoThumbnail(getData().getFilePath()));
218    }
219
220    @Override
221    public String toString() {
222        return "VideoItem: " + mData.toString();
223    }
224
225    private void renderTiny(@Nonnull VideoViewHolder viewHolder) {
226        mGlideManager.loadMediaStoreThumb(mData.getUri(), generateSignature(mData))
227              .into(viewHolder.mVideoView);
228    }
229
230    private VideoViewHolder getViewHolder(@Nonnull View view) {
231        Object container = view.getTag(R.id.mediadata_tag_target);
232        if (container instanceof VideoViewHolder) {
233            return (VideoViewHolder) container;
234        }
235
236        return null;
237    }
238}
239