1 /*
2  * Copyright (C) 2010 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.gallery3d.data;
18 
19 import android.content.ContentResolver;
20 import android.database.Cursor;
21 import android.graphics.Bitmap;
22 import android.graphics.BitmapRegionDecoder;
23 import android.net.Uri;
24 import android.provider.MediaStore.Video;
25 import android.provider.MediaStore.Video.VideoColumns;
26 
27 import com.android.gallery3d.app.GalleryApp;
28 import com.android.gallery3d.common.BitmapUtils;
29 import com.android.gallery3d.util.GalleryUtils;
30 import com.android.gallery3d.util.ThreadPool.Job;
31 import com.android.gallery3d.util.ThreadPool.JobContext;
32 import com.android.gallery3d.util.UpdateHelper;
33 
34 // LocalVideo represents a video in the local storage.
35 public class LocalVideo extends LocalMediaItem {
36     private static final String TAG = "LocalVideo";
37     static final Path ITEM_PATH = Path.fromString("/local/video/item");
38 
39     // Must preserve order between these indices and the order of the terms in
40     // the following PROJECTION array.
41     private static final int INDEX_ID = 0;
42     private static final int INDEX_CAPTION = 1;
43     private static final int INDEX_MIME_TYPE = 2;
44     private static final int INDEX_LATITUDE = 3;
45     private static final int INDEX_LONGITUDE = 4;
46     private static final int INDEX_DATE_TAKEN = 5;
47     private static final int INDEX_DATE_ADDED = 6;
48     private static final int INDEX_DATE_MODIFIED = 7;
49     private static final int INDEX_DATA = 8;
50     private static final int INDEX_DURATION = 9;
51     private static final int INDEX_BUCKET_ID = 10;
52     private static final int INDEX_SIZE = 11;
53     private static final int INDEX_RESOLUTION = 12;
54 
55     static final String[] PROJECTION = new String[] {
56             VideoColumns._ID,
57             VideoColumns.TITLE,
58             VideoColumns.MIME_TYPE,
59             VideoColumns.LATITUDE,
60             VideoColumns.LONGITUDE,
61             VideoColumns.DATE_TAKEN,
62             VideoColumns.DATE_ADDED,
63             VideoColumns.DATE_MODIFIED,
64             VideoColumns.DATA,
65             VideoColumns.DURATION,
66             VideoColumns.BUCKET_ID,
67             VideoColumns.SIZE,
68             VideoColumns.RESOLUTION,
69     };
70 
71     private final GalleryApp mApplication;
72 
73     public int durationInSec;
74 
LocalVideo(Path path, GalleryApp application, Cursor cursor)75     public LocalVideo(Path path, GalleryApp application, Cursor cursor) {
76         super(path, nextVersionNumber());
77         mApplication = application;
78         loadFromCursor(cursor);
79     }
80 
LocalVideo(Path path, GalleryApp context, int id)81     public LocalVideo(Path path, GalleryApp context, int id) {
82         super(path, nextVersionNumber());
83         mApplication = context;
84         ContentResolver resolver = mApplication.getContentResolver();
85         Uri uri = Video.Media.EXTERNAL_CONTENT_URI;
86         Cursor cursor = LocalAlbum.getItemCursor(resolver, uri, PROJECTION, id);
87         if (cursor == null) {
88             throw new RuntimeException("cannot get cursor for: " + path);
89         }
90         try {
91             if (cursor.moveToNext()) {
92                 loadFromCursor(cursor);
93             } else {
94                 throw new RuntimeException("cannot find data for: " + path);
95             }
96         } finally {
97             cursor.close();
98         }
99     }
100 
loadFromCursor(Cursor cursor)101     private void loadFromCursor(Cursor cursor) {
102         id = cursor.getInt(INDEX_ID);
103         caption = cursor.getString(INDEX_CAPTION);
104         mimeType = cursor.getString(INDEX_MIME_TYPE);
105         latitude = cursor.getDouble(INDEX_LATITUDE);
106         longitude = cursor.getDouble(INDEX_LONGITUDE);
107         dateTakenInMs = cursor.getLong(INDEX_DATE_TAKEN);
108         dateAddedInSec = cursor.getLong(INDEX_DATE_ADDED);
109         dateModifiedInSec = cursor.getLong(INDEX_DATE_MODIFIED);
110         filePath = cursor.getString(INDEX_DATA);
111         durationInSec = cursor.getInt(INDEX_DURATION) / 1000;
112         bucketId = cursor.getInt(INDEX_BUCKET_ID);
113         fileSize = cursor.getLong(INDEX_SIZE);
114         parseResolution(cursor.getString(INDEX_RESOLUTION));
115     }
116 
parseResolution(String resolution)117     private void parseResolution(String resolution) {
118         if (resolution == null) return;
119         int m = resolution.indexOf('x');
120         if (m == -1) return;
121         try {
122             int w = Integer.parseInt(resolution.substring(0, m));
123             int h = Integer.parseInt(resolution.substring(m + 1));
124             width = w;
125             height = h;
126         } catch (Throwable t) {
127             Log.w(TAG, t);
128         }
129     }
130 
131     @Override
updateFromCursor(Cursor cursor)132     protected boolean updateFromCursor(Cursor cursor) {
133         UpdateHelper uh = new UpdateHelper();
134         id = uh.update(id, cursor.getInt(INDEX_ID));
135         caption = uh.update(caption, cursor.getString(INDEX_CAPTION));
136         mimeType = uh.update(mimeType, cursor.getString(INDEX_MIME_TYPE));
137         latitude = uh.update(latitude, cursor.getDouble(INDEX_LATITUDE));
138         longitude = uh.update(longitude, cursor.getDouble(INDEX_LONGITUDE));
139         dateTakenInMs = uh.update(
140                 dateTakenInMs, cursor.getLong(INDEX_DATE_TAKEN));
141         dateAddedInSec = uh.update(
142                 dateAddedInSec, cursor.getLong(INDEX_DATE_ADDED));
143         dateModifiedInSec = uh.update(
144                 dateModifiedInSec, cursor.getLong(INDEX_DATE_MODIFIED));
145         filePath = uh.update(filePath, cursor.getString(INDEX_DATA));
146         durationInSec = uh.update(
147                 durationInSec, cursor.getInt(INDEX_DURATION) / 1000);
148         bucketId = uh.update(bucketId, cursor.getInt(INDEX_BUCKET_ID));
149         fileSize = uh.update(fileSize, cursor.getLong(INDEX_SIZE));
150         return uh.isUpdated();
151     }
152 
153     @Override
requestImage(int type)154     public Job<Bitmap> requestImage(int type) {
155         return new LocalVideoRequest(mApplication, getPath(), dateModifiedInSec,
156                 type, filePath);
157     }
158 
159     public static class LocalVideoRequest extends ImageCacheRequest {
160         private String mLocalFilePath;
161 
LocalVideoRequest(GalleryApp application, Path path, long timeModified, int type, String localFilePath)162         LocalVideoRequest(GalleryApp application, Path path, long timeModified,
163                 int type, String localFilePath) {
164             super(application, path, timeModified, type,
165                     MediaItem.getTargetSize(type));
166             mLocalFilePath = localFilePath;
167         }
168 
169         @Override
onDecodeOriginal(JobContext jc, int type)170         public Bitmap onDecodeOriginal(JobContext jc, int type) {
171             Bitmap bitmap = BitmapUtils.createVideoThumbnail(mLocalFilePath);
172             if (bitmap == null || jc.isCancelled()) return null;
173             return bitmap;
174         }
175     }
176 
177     @Override
requestLargeImage()178     public Job<BitmapRegionDecoder> requestLargeImage() {
179         throw new UnsupportedOperationException("Cannot regquest a large image"
180                 + " to a local video!");
181     }
182 
183     @Override
getSupportedOperations()184     public int getSupportedOperations() {
185         return SUPPORT_DELETE | SUPPORT_SHARE | SUPPORT_PLAY | SUPPORT_INFO | SUPPORT_TRIM | SUPPORT_MUTE;
186     }
187 
188     @Override
delete()189     public void delete() {
190         GalleryUtils.assertNotInRenderThread();
191         Uri baseUri = Video.Media.EXTERNAL_CONTENT_URI;
192         mApplication.getContentResolver().delete(baseUri, "_id=?",
193                 new String[]{String.valueOf(id)});
194     }
195 
196     @Override
rotate(int degrees)197     public void rotate(int degrees) {
198         // TODO
199     }
200 
201     @Override
getContentUri()202     public Uri getContentUri() {
203         Uri baseUri = Video.Media.EXTERNAL_CONTENT_URI;
204         return baseUri.buildUpon().appendPath(String.valueOf(id)).build();
205     }
206 
207     @Override
getPlayUri()208     public Uri getPlayUri() {
209         return getContentUri();
210     }
211 
212     @Override
getMediaType()213     public int getMediaType() {
214         return MEDIA_TYPE_VIDEO;
215     }
216 
217     @Override
getDetails()218     public MediaDetails getDetails() {
219         MediaDetails details = super.getDetails();
220         int s = durationInSec;
221         if (s > 0) {
222             details.addDetail(MediaDetails.INDEX_DURATION, GalleryUtils.formatDuration(
223                     mApplication.getAndroidContext(), durationInSec));
224         }
225         return details;
226     }
227 
228     @Override
getWidth()229     public int getWidth() {
230         return width;
231     }
232 
233     @Override
getHeight()234     public int getHeight() {
235         return height;
236     }
237 
238     @Override
getFilePath()239     public String getFilePath() {
240         return filePath;
241     }
242 }
243