2 * Copyright (C) 2013 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.camera.data;
19 import android.content.ContentResolver;
20 import android.content.Context;
21 import android.database.Cursor;
22 import android.graphics.Bitmap;
23 import android.graphics.BitmapFactory;
24 import android.media.CamcorderProfile;
25 import android.net.Uri;
26 import android.os.Bundle;
27 import android.provider.MediaStore;
28 import android.view.LayoutInflater;
29 import android.view.View;
30 import android.widget.ImageView;
32 import com.android.camera.Storage;
33 import com.android.camera.debug.Log;
34 import com.android.camera2.R;
35 import com.bumptech.glide.BitmapRequestBuilder;
36 import com.bumptech.glide.Glide;
37 import com.bumptech.glide.load.resource.bitmap.BitmapEncoder;
40 import java.text.DateFormat;
41 import java.util.ArrayList;
42 import java.util.Date;
43 import java.util.List;
44 import java.util.Locale;
47 * A base class for all the local media files. The bitmap is loaded in
48 * background thread. Subclasses should implement their own background loading
49 * thread by sub-classing BitmapLoadTask and overriding doInBackground() to
52 public abstract class LocalMediaData implements LocalData {
53 /** The minimum id to use to query for all media at a given media store uri */
54 static final int QUERY_ALL_MEDIA_ID = -1;
55 private static final String CAMERA_PATH = Storage.DIRECTORY + "%";
56 private static final String SELECT_BY_PATH = MediaStore.MediaColumns.DATA + " LIKE ?";
57 private static final int MEDIASTORE_THUMB_WIDTH = 512;
58 private static final int MEDIASTORE_THUMB_HEIGHT = 384;
60 protected final long mContentId;
61 protected final String mTitle;
62 protected final String mMimeType;
63 protected final long mDateTakenInMilliSeconds;
64 protected final long mDateModifiedInSeconds;
65 protected final String mPath;
66 // width and height should be adjusted according to orientation.
67 protected final int mWidth;
68 protected final int mHeight;
69 protected final long mSizeInBytes;
70 protected final double mLatitude;
71 protected final double mLongitude;
72 protected final Bundle mMetaData;
74 private static final int JPEG_COMPRESS_QUALITY = 90;
75 private static final BitmapEncoder JPEG_ENCODER =
76 new BitmapEncoder(Bitmap.CompressFormat.JPEG, JPEG_COMPRESS_QUALITY);
79 * Used for thumbnail loading optimization. True if this data has a
80 * corresponding visible view.
82 protected Boolean mUsing = false;
84 public LocalMediaData(long contentId, String title, String mimeType,
85 long dateTakenInMilliSeconds, long dateModifiedInSeconds, String path,
86 int width, int height, long sizeInBytes, double latitude,
88 mContentId = contentId;
91 mDateTakenInMilliSeconds = dateTakenInMilliSeconds;
92 mDateModifiedInSeconds = dateModifiedInSeconds;
96 mSizeInBytes = sizeInBytes;
98 mLongitude = longitude;
99 mMetaData = new Bundle();
102 private interface CursorToLocalData {
103 public LocalData build(Cursor cursor);
106 private static List<LocalData> queryLocalMediaData(ContentResolver contentResolver,
107 Uri contentUri, String[] projection, long minimumId, String orderBy,
108 CursorToLocalData builder) {
109 String selection = SELECT_BY_PATH + " AND " + MediaStore.MediaColumns._ID + " > ?";
110 String[] selectionArgs = new String[] { CAMERA_PATH, Long.toString(minimumId) };
112 Cursor cursor = contentResolver.query(contentUri, projection,
113 selection, selectionArgs, orderBy);
114 List<LocalData> result = new ArrayList<LocalData>();
115 if (cursor != null) {
116 while (cursor.moveToNext()) {
117 LocalData data = builder.build(cursor);
121 final int dataIndex = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
122 Log.e(TAG, "Error loading data:" + cursor.getString(dataIndex));
132 public long getDateTaken() {
133 return mDateTakenInMilliSeconds;
137 public long getDateModified() {
138 return mDateModifiedInSeconds;
142 public long getContentId() {
147 public String getTitle() {
152 public int getWidth() {
157 public int getHeight() {
162 public int getRotation() {
167 public String getPath() {
172 public long getSizeInBytes() {
177 public boolean isUIActionSupported(int action) {
182 public boolean isDataActionSupported(int action) {
187 public boolean delete(Context context) {
188 File f = new File(mPath);
193 public void onFullScreen(boolean fullScreen) {
198 public boolean canSwipeInFullScreen() {
202 protected ImageView fillImageView(Context context, ImageView v,
203 int thumbWidth, int thumbHeight, int placeHolderResourceId,
204 LocalDataAdapter adapter, boolean isInProgress) {
206 .loadFromMediaStore(getUri(), mMimeType, mDateModifiedInSeconds, 0)
208 .placeholder(placeHolderResourceId)
211 v.setContentDescription(context.getResources().getString(
212 R.string.media_date_content_description,
213 getReadableDate(mDateModifiedInSeconds)));
219 public View getView(Context context, View recycled, int thumbWidth, int thumbHeight,
220 int placeHolderResourceId, LocalDataAdapter adapter, boolean isInProgress,
221 ActionCallback actionCallback) {
222 final ImageView imageView;
223 if (recycled != null) {
224 imageView = (ImageView) recycled;
226 imageView = (ImageView) LayoutInflater.from(context)
227 .inflate(R.layout.filmstrip_image, null);
228 imageView.setTag(R.id.mediadata_tag_viewtype, getItemViewType().ordinal());
231 return fillImageView(context, imageView, thumbWidth, thumbHeight,
232 placeHolderResourceId, adapter, isInProgress);
236 public void loadFullImage(Context context, int thumbWidth, int thumbHeight, View view,
237 LocalDataAdapter adapter) {
238 // Default is do nothing.
239 // Can be implemented by sub-classes.
243 public void prepare() {
244 synchronized (mUsing) {
250 public void recycle(View view) {
251 synchronized (mUsing) {
257 public double[] getLatLong() {
258 if (mLatitude == 0 && mLongitude == 0) {
261 return new double[] {
262 mLatitude, mLongitude
266 protected boolean isUsing() {
267 synchronized (mUsing) {
273 public String getMimeType() {
278 public MediaDetails getMediaDetails(Context context) {
279 MediaDetails mediaDetails = new MediaDetails();
280 mediaDetails.addDetail(MediaDetails.INDEX_TITLE, mTitle);
281 mediaDetails.addDetail(MediaDetails.INDEX_WIDTH, mWidth);
282 mediaDetails.addDetail(MediaDetails.INDEX_HEIGHT, mHeight);
283 mediaDetails.addDetail(MediaDetails.INDEX_PATH, mPath);
284 mediaDetails.addDetail(MediaDetails.INDEX_DATETIME,
285 getReadableDate(mDateModifiedInSeconds));
286 if (mSizeInBytes > 0) {
287 mediaDetails.addDetail(MediaDetails.INDEX_SIZE, mSizeInBytes);
289 if (mLatitude != 0 && mLongitude != 0) {
290 String locationString = String.format(Locale.getDefault(), "%f, %f", mLatitude,
292 mediaDetails.addDetail(MediaDetails.INDEX_LOCATION, locationString);
297 private static String getReadableDate(long dateInSeconds) {
298 DateFormat dateFormatter = DateFormat.getDateTimeInstance();
299 return dateFormatter.format(new Date(dateInSeconds * 1000));
303 public abstract int getViewType();
306 public Bundle getMetadata() {
311 public boolean isMetadataUpdated() {
312 return MetadataLoader.isMetadataCached(this);
315 public static final class PhotoData extends LocalMediaData {
316 private static final Log.Tag TAG = new Log.Tag("PhotoData");
318 public static final int COL_ID = 0;
319 public static final int COL_TITLE = 1;
320 public static final int COL_MIME_TYPE = 2;
321 public static final int COL_DATE_TAKEN = 3;
322 public static final int COL_DATE_MODIFIED = 4;
323 public static final int COL_DATA = 5;
324 public static final int COL_ORIENTATION = 6;
325 public static final int COL_WIDTH = 7;
326 public static final int COL_HEIGHT = 8;
327 public static final int COL_SIZE = 9;
328 public static final int COL_LATITUDE = 10;
329 public static final int COL_LONGITUDE = 11;
331 // GL max texture size: keep bitmaps below this value.
332 private static final int MAXIMUM_TEXTURE_SIZE = 2048;
334 static final Uri CONTENT_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
336 // Sort all data by ID. This must be aligned with
337 // {@link CameraDataAdapter.QueryTask} which relies on the highest ID
338 // being first in any data returned.
339 private static final String QUERY_ORDER = MediaStore.Images.ImageColumns._ID + " DESC";
341 * These values should be kept in sync with column IDs (COL_*) above.
343 private static final String[] QUERY_PROJECTION = {
344 MediaStore.Images.ImageColumns._ID, // 0, int
345 MediaStore.Images.ImageColumns.TITLE, // 1, string
346 MediaStore.Images.ImageColumns.MIME_TYPE, // 2, string
347 MediaStore.Images.ImageColumns.DATE_TAKEN, // 3, int
348 MediaStore.Images.ImageColumns.DATE_MODIFIED, // 4, int
349 MediaStore.Images.ImageColumns.DATA, // 5, string
350 MediaStore.Images.ImageColumns.ORIENTATION, // 6, int, 0, 90, 180, 270
351 MediaStore.Images.ImageColumns.WIDTH, // 7, int
352 MediaStore.Images.ImageColumns.HEIGHT, // 8, int
353 MediaStore.Images.ImageColumns.SIZE, // 9, long
354 MediaStore.Images.ImageColumns.LATITUDE, // 10, double
355 MediaStore.Images.ImageColumns.LONGITUDE // 11, double
358 private static final int mSupportedUIActions = ACTION_DEMOTE | ACTION_PROMOTE | ACTION_ZOOM;
359 private static final int mSupportedDataActions =
360 DATA_ACTION_DELETE | DATA_ACTION_EDIT | DATA_ACTION_SHARE;
362 /** from MediaStore, can only be 0, 90, 180, 270 */
363 private final int mOrientation;
364 /** @see #getSignature() */
365 private final String mSignature;
367 public static LocalData fromContentUri(ContentResolver cr, Uri contentUri) {
368 List<LocalData> newPhotos = query(cr, contentUri, QUERY_ALL_MEDIA_ID);
369 if (newPhotos.isEmpty()) {
372 return newPhotos.get(0);
375 public PhotoData(long id, String title, String mimeType,
376 long dateTakenInMilliSeconds, long dateModifiedInSeconds,
377 String path, int orientation, int width, int height,
378 long sizeInBytes, double latitude, double longitude) {
379 super(id, title, mimeType, dateTakenInMilliSeconds, dateModifiedInSeconds,
380 path, width, height, sizeInBytes, latitude, longitude);
381 mOrientation = orientation;
382 mSignature = mimeType + orientation + dateModifiedInSeconds;
385 static List<LocalData> query(ContentResolver cr, Uri uri, long lastId) {
386 return queryLocalMediaData(cr, uri, QUERY_PROJECTION, lastId, QUERY_ORDER,
387 new PhotoDataBuilder());
390 private static PhotoData buildFromCursor(Cursor c) {
391 long id = c.getLong(COL_ID);
392 String title = c.getString(COL_TITLE);
393 String mimeType = c.getString(COL_MIME_TYPE);
394 long dateTakenInMilliSeconds = c.getLong(COL_DATE_TAKEN);
395 long dateModifiedInSeconds = c.getLong(COL_DATE_MODIFIED);
396 String path = c.getString(COL_DATA);
397 int orientation = c.getInt(COL_ORIENTATION);
398 int width = c.getInt(COL_WIDTH);
399 int height = c.getInt(COL_HEIGHT);
400 if (width <= 0 || height <= 0) {
401 Log.w(TAG, "Zero dimension in ContentResolver for "
402 + path + ":" + width + "x" + height);
403 BitmapFactory.Options opts = new BitmapFactory.Options();
404 opts.inJustDecodeBounds = true;
405 BitmapFactory.decodeFile(path, opts);
406 if (opts.outWidth > 0 && opts.outHeight > 0) {
407 width = opts.outWidth;
408 height = opts.outHeight;
410 Log.w(TAG, "Dimension decode failed for " + path);
411 Bitmap b = BitmapFactory.decodeFile(path);
413 Log.w(TAG, "PhotoData skipped."
414 + " Decoding " + path + "failed.");
417 width = b.getWidth();
418 height = b.getHeight();
419 if (width == 0 || height == 0) {
420 Log.w(TAG, "PhotoData skipped. Bitmap size 0 for " + path);
426 long sizeInBytes = c.getLong(COL_SIZE);
427 double latitude = c.getDouble(COL_LATITUDE);
428 double longitude = c.getDouble(COL_LONGITUDE);
429 PhotoData result = new PhotoData(id, title, mimeType, dateTakenInMilliSeconds,
430 dateModifiedInSeconds, path, orientation, width, height,
431 sizeInBytes, latitude, longitude);
436 public int getRotation() {
441 public String toString() {
442 return "Photo:" + ",data=" + mPath + ",mimeType=" + mMimeType
443 + "," + mWidth + "x" + mHeight + ",orientation=" + mOrientation
444 + ",date=" + new Date(mDateTakenInMilliSeconds);
448 public int getViewType() {
449 return VIEW_TYPE_REMOVABLE;
453 public boolean isUIActionSupported(int action) {
454 return ((action & mSupportedUIActions) == action);
458 public boolean isDataActionSupported(int action) {
459 return ((action & mSupportedDataActions) == action);
463 public boolean delete(Context context) {
464 ContentResolver cr = context.getContentResolver();
465 cr.delete(CONTENT_URI, MediaStore.Images.ImageColumns._ID + "=" + mContentId, null);
466 return super.delete(context);
470 public Uri getUri() {
471 Uri baseUri = CONTENT_URI;
472 return baseUri.buildUpon().appendPath(String.valueOf(mContentId)).build();
476 public MediaDetails getMediaDetails(Context context) {
477 MediaDetails mediaDetails = super.getMediaDetails(context);
478 MediaDetails.extractExifInfo(mediaDetails, mPath);
479 mediaDetails.addDetail(MediaDetails.INDEX_ORIENTATION, mOrientation);
484 public int getLocalDataType() {
489 public LocalData refresh(Context context) {
490 PhotoData newData = null;
491 Cursor c = context.getContentResolver().query(getUri(), QUERY_PROJECTION, null,
494 if (c.moveToFirst()) {
495 newData = buildFromCursor(c);
504 public String getSignature() {
509 protected ImageView fillImageView(Context context, final ImageView v, final int thumbWidth,
510 final int thumbHeight, int placeHolderResourceId, LocalDataAdapter adapter,
511 boolean isInProgress) {
512 loadImage(context, v, thumbWidth, thumbHeight, placeHolderResourceId, false);
514 int stringId = R.string.photo_date_content_description;
515 if (PanoramaMetadataLoader.isPanorama(this) ||
516 PanoramaMetadataLoader.isPanorama360(this)) {
517 stringId = R.string.panorama_date_content_description;
518 } else if (PanoramaMetadataLoader.isPanoramaAndUseViewer(this)) {
519 // assume it's a PhotoSphere
520 stringId = R.string.photosphere_date_content_description;
521 } else if (RgbzMetadataLoader.hasRGBZData(this)) {
522 stringId = R.string.refocus_date_content_description;
525 v.setContentDescription(context.getResources().getString(
527 getReadableDate(mDateModifiedInSeconds)));
532 private void loadImage(Context context, ImageView imageView, int thumbWidth,
533 int thumbHeight, int placeHolderResourceId, boolean full) {
535 //TODO: Figure out why these can be <= 0.
536 if (thumbWidth <= 0 || thumbHeight <=0) {
540 final int overrideWidth;
541 final int overrideHeight;
542 final BitmapRequestBuilder<Uri, Bitmap> thumbnailRequest;
544 // Load up to the maximum size Bitmap we can render.
545 overrideWidth = Math.min(getWidth(), MAXIMUM_TEXTURE_SIZE);
546 overrideHeight = Math.min(getHeight(), MAXIMUM_TEXTURE_SIZE);
548 // Load two thumbnails, first the small low quality thumb from the media store,
549 // then a medium quality thumbWidth/thumbHeight image. Using two thumbnails ensures
550 // we don't flicker to grey while we load the maximum size image.
551 thumbnailRequest = loadUri(context)
552 .override(thumbWidth, thumbHeight)
554 .thumbnail(loadMediaStoreThumb(context));
556 // Load a medium quality thumbWidth/thumbHeight image.
557 overrideWidth = thumbWidth;
558 overrideHeight = thumbHeight;
560 // Load a single small low quality thumbnail from the media store.
561 thumbnailRequest = loadMediaStoreThumb(context);
565 .placeholder(placeHolderResourceId)
567 .override(overrideWidth, overrideHeight)
568 .thumbnail(thumbnailRequest)
572 /** Loads a thumbnail with a size targeted to use MediaStore.Images.Thumbnails. */
573 private BitmapRequestBuilder<Uri, Bitmap> loadMediaStoreThumb(Context context) {
574 return loadUri(context)
575 .override(MEDIASTORE_THUMB_WIDTH, MEDIASTORE_THUMB_HEIGHT);
578 /** Loads an image using a MediaStore Uri with our default options. */
579 private BitmapRequestBuilder<Uri, Bitmap> loadUri(Context context) {
580 return Glide.with(context)
581 .loadFromMediaStore(getUri(), mMimeType, mDateModifiedInSeconds, mOrientation)
583 .encoder(JPEG_ENCODER);
587 public void recycle(View view) {
595 public LocalDataViewType getItemViewType() {
596 return LocalDataViewType.PHOTO;
600 public void loadFullImage(Context context, int thumbWidth, int thumbHeight, View v,
601 LocalDataAdapter adapter)
603 loadImage(context, (ImageView) v, thumbWidth, thumbHeight, 0, true);
606 private static class PhotoDataBuilder implements CursorToLocalData {
608 public PhotoData build(Cursor cursor) {
609 return LocalMediaData.PhotoData.buildFromCursor(cursor);
614 public static final class VideoData extends LocalMediaData {
615 public static final int COL_ID = 0;
616 public static final int COL_TITLE = 1;
617 public static final int COL_MIME_TYPE = 2;
618 public static final int COL_DATE_TAKEN = 3;
619 public static final int COL_DATE_MODIFIED = 4;
620 public static final int COL_DATA = 5;
621 public static final int COL_WIDTH = 6;
622 public static final int COL_HEIGHT = 7;
623 public static final int COL_SIZE = 8;
624 public static final int COL_LATITUDE = 9;
625 public static final int COL_LONGITUDE = 10;
626 public static final int COL_DURATION = 11;
628 static final Uri CONTENT_URI = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
630 private static final int mSupportedUIActions = ACTION_DEMOTE | ACTION_PROMOTE;
631 private static final int mSupportedDataActions =
632 DATA_ACTION_DELETE | DATA_ACTION_PLAY | DATA_ACTION_SHARE;
634 private static final String QUERY_ORDER = MediaStore.Video.VideoColumns.DATE_TAKEN
635 + " DESC, " + MediaStore.Video.VideoColumns._ID + " DESC";
637 * These values should be kept in sync with column IDs (COL_*) above.
639 private static final String[] QUERY_PROJECTION = {
640 MediaStore.Video.VideoColumns._ID, // 0, int
641 MediaStore.Video.VideoColumns.TITLE, // 1, string
642 MediaStore.Video.VideoColumns.MIME_TYPE, // 2, string
643 MediaStore.Video.VideoColumns.DATE_TAKEN, // 3, int
644 MediaStore.Video.VideoColumns.DATE_MODIFIED, // 4, int
645 MediaStore.Video.VideoColumns.DATA, // 5, string
646 MediaStore.Video.VideoColumns.WIDTH, // 6, int
647 MediaStore.Video.VideoColumns.HEIGHT, // 7, int
648 MediaStore.Video.VideoColumns.SIZE, // 8 long
649 MediaStore.Video.VideoColumns.LATITUDE, // 9 double
650 MediaStore.Video.VideoColumns.LONGITUDE, // 10 double
651 MediaStore.Video.VideoColumns.DURATION // 11 long
654 /** The duration in milliseconds. */
655 private final long mDurationInSeconds;
656 private final String mSignature;
658 public VideoData(long id, String title, String mimeType,
659 long dateTakenInMilliSeconds, long dateModifiedInSeconds,
660 String path, int width, int height, long sizeInBytes,
661 double latitude, double longitude, long durationInSeconds) {
662 super(id, title, mimeType, dateTakenInMilliSeconds, dateModifiedInSeconds,
663 path, width, height, sizeInBytes, latitude, longitude);
664 mDurationInSeconds = durationInSeconds;
665 mSignature = mimeType + dateModifiedInSeconds;
668 public static LocalData fromContentUri(ContentResolver cr, Uri contentUri) {
669 List<LocalData> newVideos = query(cr, contentUri, QUERY_ALL_MEDIA_ID);
670 if (newVideos.isEmpty()) {
673 return newVideos.get(0);
676 static List<LocalData> query(ContentResolver cr, Uri uri, long lastId) {
677 return queryLocalMediaData(cr, uri, QUERY_PROJECTION, lastId, QUERY_ORDER,
678 new VideoDataBuilder());
682 * We can't trust the media store and we can't afford the performance overhead of
683 * synchronously decoding the video header for every item when loading our data set
684 * from the media store, so we instead run the metadata loader in the background
685 * to decode the video header for each item and prefer whatever values it obtains.
687 private int getBestWidth() {
688 int metadataWidth = VideoRotationMetadataLoader.getWidth(this);
689 if (metadataWidth > 0) {
690 return metadataWidth;
696 private int getBestHeight() {
697 int metadataHeight = VideoRotationMetadataLoader.getHeight(this);
698 if (metadataHeight > 0) {
699 return metadataHeight;
706 * If the metadata loader has determined from the video header that we need to rotate the video
707 * 90 or 270 degrees, then we swap the width and height.
710 public int getWidth() {
711 return VideoRotationMetadataLoader.isRotated(this) ? getBestHeight() : getBestWidth();
715 public int getHeight() {
716 return VideoRotationMetadataLoader.isRotated(this) ? getBestWidth() : getBestHeight();
719 private static VideoData buildFromCursor(Cursor c) {
720 long id = c.getLong(COL_ID);
721 String title = c.getString(COL_TITLE);
722 String mimeType = c.getString(COL_MIME_TYPE);
723 long dateTakenInMilliSeconds = c.getLong(COL_DATE_TAKEN);
724 long dateModifiedInSeconds = c.getLong(COL_DATE_MODIFIED);
725 String path = c.getString(COL_DATA);
726 int width = c.getInt(COL_WIDTH);
727 int height = c.getInt(COL_HEIGHT);
729 // If the media store doesn't contain a width and a height, use the width and height
730 // of the default camera mode instead. When the metadata loader runs, it will set the
732 if (width == 0 || height == 0) {
733 Log.w(TAG, "failed to retrieve width and height from the media store, defaulting " +
734 " to camera profile");
735 CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
736 width = profile.videoFrameWidth;
737 height = profile.videoFrameHeight;
740 long sizeInBytes = c.getLong(COL_SIZE);
741 double latitude = c.getDouble(COL_LATITUDE);
742 double longitude = c.getDouble(COL_LONGITUDE);
743 long durationInSeconds = c.getLong(COL_DURATION) / 1000;
744 VideoData d = new VideoData(id, title, mimeType, dateTakenInMilliSeconds,
745 dateModifiedInSeconds, path, width, height, sizeInBytes,
746 latitude, longitude, durationInSeconds);
751 public String toString() {
752 return "Video:" + ",data=" + mPath + ",mimeType=" + mMimeType
753 + "," + mWidth + "x" + mHeight + ",date=" + new Date(mDateTakenInMilliSeconds);
757 public int getViewType() {
758 return VIEW_TYPE_REMOVABLE;
762 public boolean isUIActionSupported(int action) {
763 return ((action & mSupportedUIActions) == action);
767 public boolean isDataActionSupported(int action) {
768 return ((action & mSupportedDataActions) == action);
772 public boolean delete(Context context) {
773 ContentResolver cr = context.getContentResolver();
774 cr.delete(CONTENT_URI, MediaStore.Video.VideoColumns._ID + "=" + mContentId, null);
775 return super.delete(context);
779 public Uri getUri() {
780 Uri baseUri = CONTENT_URI;
781 return baseUri.buildUpon().appendPath(String.valueOf(mContentId)).build();
785 public MediaDetails getMediaDetails(Context context) {
786 MediaDetails mediaDetails = super.getMediaDetails(context);
787 String duration = MediaDetails.formatDuration(context, mDurationInSeconds);
788 mediaDetails.addDetail(MediaDetails.INDEX_DURATION, duration);
793 public int getLocalDataType() {
798 public LocalData refresh(Context context) {
799 Cursor c = context.getContentResolver().query(getUri(), QUERY_PROJECTION, null,
801 if (c == null || !c.moveToFirst()) {
804 VideoData newData = buildFromCursor(c);
809 public String getSignature() {
814 protected ImageView fillImageView(Context context, final ImageView v, final int thumbWidth,
815 final int thumbHeight, int placeHolderResourceId, LocalDataAdapter adapter,
816 boolean isInProgress) {
818 //TODO: Figure out why these can be <= 0.
819 if (thumbWidth <= 0 || thumbHeight <=0) {
824 .loadFromMediaStore(getUri(), mMimeType, mDateModifiedInSeconds, 0)
826 .encoder(JPEG_ENCODER)
827 .thumbnail(Glide.with(context)
828 .loadFromMediaStore(getUri(), mMimeType, mDateModifiedInSeconds, 0)
830 .encoder(JPEG_ENCODER)
831 .override(MEDIASTORE_THUMB_WIDTH, MEDIASTORE_THUMB_HEIGHT))
832 .placeholder(placeHolderResourceId)
834 .override(thumbWidth, thumbHeight)
837 // Content descriptions applied to parent FrameView
844 public View getView(final Context context, View recycled,
845 int thumbWidth, int thumbHeight, int placeHolderResourceId,
846 LocalDataAdapter adapter, boolean isInProgress,
847 final ActionCallback actionCallback) {
849 final VideoViewHolder viewHolder;
851 if (recycled != null) {
853 viewHolder = (VideoViewHolder) recycled.getTag(R.id.mediadata_tag_target);
855 result = LayoutInflater.from(context).inflate(R.layout.filmstrip_video, null);
856 result.setTag(R.id.mediadata_tag_viewtype, getItemViewType().ordinal());
857 ImageView videoView = (ImageView) result.findViewById(R.id.video_view);
858 ImageView playButton = (ImageView) result.findViewById(R.id.play_button);
859 viewHolder = new VideoViewHolder(videoView, playButton);
860 result.setTag(R.id.mediadata_tag_target, viewHolder);
863 fillImageView(context, viewHolder.mVideoView, thumbWidth, thumbHeight,
864 placeHolderResourceId, adapter, isInProgress);
866 // ImageView for the play icon.
867 viewHolder.mPlayButton.setOnClickListener(new View.OnClickListener() {
869 public void onClick(View v) {
870 actionCallback.playVideo(getUri(), mTitle);
874 result.setContentDescription(context.getResources().getString(
875 R.string.video_date_content_description,
876 getReadableDate(mDateModifiedInSeconds)));
882 public void recycle(View view) {
884 VideoViewHolder videoViewHolder =
885 (VideoViewHolder) view.getTag(R.id.mediadata_tag_target);
886 Glide.clear(videoViewHolder.mVideoView);
890 public LocalDataViewType getItemViewType() {
891 return LocalDataViewType.VIDEO;
895 private static class VideoDataBuilder implements CursorToLocalData {
898 public VideoData build(Cursor cursor) {
899 return LocalMediaData.VideoData.buildFromCursor(cursor);
903 private static class VideoViewHolder {
904 private final ImageView mVideoView;
905 private final ImageView mPlayButton;
907 public VideoViewHolder(ImageView videoView, ImageView playButton) {
908 mVideoView = videoView;
909 mPlayButton = playButton;