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 
17 package com.android.camera.data;
18 
19 import android.content.ContentResolver;
20 import android.content.Context;
21 import android.graphics.Bitmap;
22 import android.provider.MediaStore;
23 import android.view.LayoutInflater;
24 import android.view.View;
25 import android.widget.ImageView;
26 
27 import com.android.camera.data.FilmstripItemAttributes.Attributes;
28 import com.android.camera.debug.Log;
29 import com.android.camera.util.Size;
30 import com.android.camera2.R;
31 import com.bumptech.glide.Glide;
32 import com.google.common.base.Optional;
33 
34 import java.util.concurrent.TimeUnit;
35 
36 import javax.annotation.Nonnull;
37 
38 /**
39  * Backing data for a single video displayed in the filmstrip.
40  */
41 public class VideoItem extends FilmstripItemBase<VideoItemData> {
42     private static class VideoViewHolder {
43         private final ImageView mVideoView;
44         private final ImageView mPlayButton;
45 
VideoViewHolder(ImageView videoView, ImageView playButton)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 
VideoItem(Context context, GlideFilmstripManager manager, VideoItemData data, VideoItemFactory videoItemFactory)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      */
getBestWidth()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 
getBestHeight()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      */
getWidth()102     public int getWidth() {
103         return mMetaData.isVideoRotated() ? getBestHeight() : getBestWidth();
104     }
105 
getHeight()106     public int getHeight() {
107         return mMetaData.isVideoRotated() ?  getBestWidth() : getBestHeight();
108     }
109 
110     @Override
getDimensions()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
delete()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
getMediaDetails()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
refresh()142     public FilmstripItem refresh() {
143         return mVideoItemFactory.get(mData.getUri());
144     }
145 
146     @Override
getView(Optional<View> optionalView, LocalFilmstripDataAdapter adapter, boolean isInProgress, final VideoClickedCallback videoClickedCallback)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
renderTiny(@onnull View view)189     public void renderTiny(@Nonnull View view) {
190         renderTiny(getViewHolder(view));
191     }
192 
193     @Override
renderThumbnail(@onnull View view)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
renderFullRes(@onnull View view)202     public void renderFullRes(@Nonnull View view) { }
203 
204     @Override
recycle(@onnull View view)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
getItemViewType()213     public FilmstripItemType getItemViewType() {
214         return FilmstripItemType.VIDEO;
215     }
216 
217     @Override
generateThumbnail(int boundingWidthPx, int boundingHeightPx)218     public Optional<Bitmap> generateThumbnail(int boundingWidthPx, int boundingHeightPx) {
219         return Optional.fromNullable(FilmstripItemUtils.loadVideoThumbnail(
220                 getData().getFilePath()));
221     }
222 
223     @Override
toString()224     public String toString() {
225         return "VideoItem: " + mData.toString();
226     }
227 
renderTiny(@onnull VideoViewHolder viewHolder)228     private void renderTiny(@Nonnull VideoViewHolder viewHolder) {
229         mGlideManager.loadMediaStoreThumb(mData.getUri(), generateSignature(mData))
230               .into(viewHolder.mVideoView);
231     }
232 
getViewHolder(@onnull View view)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