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