blob: 704e8edc91e7c707e9530a2bbfad7fe50b42adb0 [file] [log] [blame]
Angus Kong750e8ec2013-05-06 10:42:28 -07001/*
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.Context;
20import android.database.Cursor;
21import android.graphics.Bitmap;
22import android.graphics.BitmapFactory;
23import android.graphics.Matrix;
Angus Kong61544542013-06-03 12:48:16 -070024import android.graphics.drawable.BitmapDrawable;
Angus Kong750e8ec2013-05-06 10:42:28 -070025import android.graphics.drawable.Drawable;
26import android.media.MediaMetadataRetriever;
Angus Kongb40738a2013-06-03 14:55:34 -070027import android.net.Uri;
Angus Kong750e8ec2013-05-06 10:42:28 -070028import android.os.AsyncTask;
Angus Kongb40738a2013-06-03 14:55:34 -070029import android.provider.MediaStore;
Angus Kong750e8ec2013-05-06 10:42:28 -070030import android.provider.MediaStore.Images.ImageColumns;
31import android.provider.MediaStore.Video;
32import android.provider.MediaStore.Video.VideoColumns;
33import android.util.Log;
Angus Kongb40738a2013-06-03 14:55:34 -070034import android.view.Gravity;
Angus Kong750e8ec2013-05-06 10:42:28 -070035import android.view.View;
Angus Kongb40738a2013-06-03 14:55:34 -070036import android.view.View.OnClickListener;
37import android.view.ViewGroup;
38import android.widget.FrameLayout;
Angus Kong750e8ec2013-05-06 10:42:28 -070039import android.widget.ImageView;
40
Angus Kongb40738a2013-06-03 14:55:34 -070041import com.android.camera.Util;
Angus Kong750e8ec2013-05-06 10:42:28 -070042import com.android.camera.ui.FilmStripView;
Angus Kong61544542013-06-03 12:48:16 -070043import com.android.gallery3d.R;
Angus Kong750e8ec2013-05-06 10:42:28 -070044
45import java.util.Comparator;
46import java.util.Date;
47
48/* An abstract interface that represents the local media data. Also implements
49 * Comparable interface so we can sort in DataAdapter.
50 */
Angus Konga06b4e22013-06-03 11:10:11 -070051public abstract interface LocalData extends FilmStripView.ImageData {
Angus Kong750e8ec2013-05-06 10:42:28 -070052 static final String TAG = "LocalData";
53
54 abstract View getView(Context c, int width, int height, Drawable placeHolder);
55 abstract long getDateTaken();
56 abstract long getDateModified();
57 abstract String getTitle();
58
59 static class NewestFirstComparator implements Comparator<LocalData> {
60
Angus Kongdff672d2013-05-22 17:30:56 -070061 // Compare taken/modified date of LocalData in descent order to make
62 // newer data in the front.
63 // The negavive numbers here are always considered "bigger" than
64 // postive ones. Thus, if any one of the numbers is negative, the logic
65 // is reversed.
66 private static int compareDate(long v1, long v2) {
67 if (v1 >= 0 && v2 >= 0) {
Angus Kong7283c602013-05-29 17:06:04 -070068 return ((v1 < v2) ? 1 : ((v1 > v2) ? -1 : 0));
Angus Kong750e8ec2013-05-06 10:42:28 -070069 }
Angus Kong7283c602013-05-29 17:06:04 -070070 return ((v2 < v1) ? 1 : ((v2 > v1) ? -1 : 0));
Angus Kong750e8ec2013-05-06 10:42:28 -070071 }
72
73 @Override
74 public int compare(LocalData d1, LocalData d2) {
Angus Kongdff672d2013-05-22 17:30:56 -070075 int cmp = compareDate(d1.getDateTaken(), d2.getDateTaken());
Angus Kong750e8ec2013-05-06 10:42:28 -070076 if (cmp == 0) {
Angus Kongdff672d2013-05-22 17:30:56 -070077 cmp = compareDate(d1.getDateModified(), d2.getDateModified());
Angus Kong750e8ec2013-05-06 10:42:28 -070078 }
79 if (cmp == 0) {
80 cmp = d1.getTitle().compareTo(d2.getTitle());
81 }
82 return cmp;
83 }
84 }
85
86 /*
87 * A base class for all the local media files. The bitmap is loaded in background
88 * thread. Subclasses should implement their own background loading thread by
89 * subclassing BitmapLoadTask and overriding doInBackground() to return a bitmap.
90 */
91 abstract static class LocalMediaData implements LocalData {
92 protected long id;
93 protected String title;
94 protected String mimeType;
95 protected long dateTaken;
96 protected long dateModified;
97 protected String path;
98 // width and height should be adjusted according to orientation.
99 protected int width;
100 protected int height;
101
102 // true if this data has a corresponding visible view.
103 protected Boolean mUsing = false;
104
105 @Override
106 public long getDateTaken() {
107 return dateTaken;
108 }
109
110 @Override
111 public long getDateModified() {
112 return dateModified;
113 }
114
115 @Override
116 public String getTitle() {
117 return new String(title);
118 }
119
120 @Override
121 public int getWidth() {
122 return width;
123 }
124
125 @Override
126 public int getHeight() {
127 return height;
128 }
129
130 @Override
131 public boolean isActionSupported(int action) {
132 return false;
133 }
134
Angus Kongb40738a2013-06-03 14:55:34 -0700135 protected View fillViewBackground(Context c, View v,
Angus Kong750e8ec2013-05-06 10:42:28 -0700136 int decodeWidth, int decodeHeight, Drawable placeHolder) {
Angus Kong61544542013-06-03 12:48:16 -0700137 v.setBackground(placeHolder);
Angus Kong750e8ec2013-05-06 10:42:28 -0700138
Angus Kong750e8ec2013-05-06 10:42:28 -0700139 BitmapLoadTask task = getBitmapLoadTask(v, decodeWidth, decodeHeight);
140 task.execute();
141 return v;
142 }
143
144 @Override
Angus Kong61544542013-06-03 12:48:16 -0700145 public View getView(Context c,
146 int decodeWidth, int decodeHeight, Drawable placeHolder) {
Angus Kongb40738a2013-06-03 14:55:34 -0700147 return fillViewBackground(c, new ImageView(c), decodeWidth, decodeHeight, placeHolder);
Angus Kong61544542013-06-03 12:48:16 -0700148 }
149
150 @Override
Angus Kong750e8ec2013-05-06 10:42:28 -0700151 public void prepare() {
152 synchronized (mUsing) {
153 mUsing = true;
154 }
155 }
156
157 @Override
158 public void recycle() {
159 synchronized (mUsing) {
160 mUsing = false;
161 }
162 }
163
164 protected boolean isUsing() {
165 synchronized (mUsing) {
166 return mUsing;
167 }
168 }
169
170 @Override
171 public abstract int getType();
172
173 protected abstract BitmapLoadTask getBitmapLoadTask(
Angus Kongb40738a2013-06-03 14:55:34 -0700174 View v, int decodeWidth, int decodeHeight);
Angus Kong750e8ec2013-05-06 10:42:28 -0700175
176 /*
177 * An AsyncTask class that loads the bitmap in the background thread.
178 * Sub-classes should implement their own "protected Bitmap doInBackground(Void... )"
179 */
180 protected abstract class BitmapLoadTask extends AsyncTask<Void, Void, Bitmap> {
Angus Kongb40738a2013-06-03 14:55:34 -0700181 protected View mView;
Angus Kong750e8ec2013-05-06 10:42:28 -0700182
Angus Kongb40738a2013-06-03 14:55:34 -0700183 protected BitmapLoadTask(View v) {
Angus Kong750e8ec2013-05-06 10:42:28 -0700184 mView = v;
185 }
186
187 @Override
188 protected void onPostExecute(Bitmap bitmap) {
189 if (!isUsing()) return;
190 if (bitmap == null) {
191 Log.e(TAG, "Failed decoding bitmap for file:" + path);
192 return;
193 }
Angus Kong61544542013-06-03 12:48:16 -0700194 BitmapDrawable d = new BitmapDrawable(bitmap);
Angus Kongb40738a2013-06-03 14:55:34 -0700195 d.setGravity(Gravity.FILL);
Angus Kong61544542013-06-03 12:48:16 -0700196 mView.setBackground(d);
Angus Kong750e8ec2013-05-06 10:42:28 -0700197 }
198 }
199 }
200
201 static class Photo extends LocalMediaData {
202 public static final int COL_ID = 0;
203 public static final int COL_TITLE = 1;
204 public static final int COL_MIME_TYPE = 2;
205 public static final int COL_DATE_TAKEN = 3;
206 public static final int COL_DATE_MODIFIED = 4;
207 public static final int COL_DATA = 5;
208 public static final int COL_ORIENTATION = 6;
209 public static final int COL_WIDTH = 7;
210 public static final int COL_HEIGHT = 8;
211
Angus Kongb40738a2013-06-03 14:55:34 -0700212 static final Uri CONTENT_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
213
Angus Kong750e8ec2013-05-06 10:42:28 -0700214 static final String QUERY_ORDER = ImageColumns.DATE_TAKEN + " DESC, "
215 + ImageColumns._ID + " DESC";
216 static final String[] QUERY_PROJECTION = {
217 ImageColumns._ID, // 0, int
218 ImageColumns.TITLE, // 1, string
219 ImageColumns.MIME_TYPE, // 2, string
220 ImageColumns.DATE_TAKEN, // 3, int
221 ImageColumns.DATE_MODIFIED, // 4, int
222 ImageColumns.DATA, // 5, string
223 ImageColumns.ORIENTATION, // 6, int, 0, 90, 180, 270
224 ImageColumns.WIDTH, // 7, int
225 ImageColumns.HEIGHT, // 8, int
226 };
227
Angus Kongb40738a2013-06-03 14:55:34 -0700228 private static final int mSupportedActions =
Angus Kong87f9a622013-05-16 16:59:39 -0700229 FilmStripView.ImageData.ACTION_DEMOTE
230 | FilmStripView.ImageData.ACTION_PROMOTE;
231
Angus Kong750e8ec2013-05-06 10:42:28 -0700232 // 32K buffer.
233 private static final byte[] DECODE_TEMP_STORAGE = new byte[32 * 1024];
234
235 // from MediaStore, can only be 0, 90, 180, 270;
236 public int orientation;
237
238 static Photo buildFromCursor(Cursor c) {
239 Photo d = new Photo();
240 d.id = c.getLong(COL_ID);
241 d.title = c.getString(COL_TITLE);
242 d.mimeType = c.getString(COL_MIME_TYPE);
243 d.dateTaken = c.getLong(COL_DATE_TAKEN);
244 d.dateModified = c.getLong(COL_DATE_MODIFIED);
245 d.path = c.getString(COL_DATA);
246 d.orientation = c.getInt(COL_ORIENTATION);
247 d.width = c.getInt(COL_WIDTH);
248 d.height = c.getInt(COL_HEIGHT);
249 if (d.width <= 0 || d.height <= 0) {
250 Log.v(TAG, "warning! zero dimension for "
251 + d.path + ":" + d.width + "x" + d.height);
252 BitmapFactory.Options opts = decodeDimension(d.path);
253 if (opts != null) {
254 d.width = opts.outWidth;
255 d.height = opts.outHeight;
256 } else {
257 Log.v(TAG, "warning! dimension decode failed for " + d.path);
258 Bitmap b = BitmapFactory.decodeFile(d.path);
259 if (b == null) {
260 return null;
261 }
262 d.width = b.getWidth();
263 d.height = b.getHeight();
264 }
265 }
266 if (d.orientation == 90 || d.orientation == 270) {
267 int b = d.width;
268 d.width = d.height;
269 d.height = b;
270 }
271 return d;
272 }
273
274 @Override
275 public String toString() {
276 return "Photo:" + ",data=" + path + ",mimeType=" + mimeType
277 + "," + width + "x" + height + ",orientation=" + orientation
278 + ",date=" + new Date(dateTaken);
279 }
280
281 @Override
282 public int getType() {
283 return TYPE_PHOTO;
284 }
285
286 @Override
Angus Kong87f9a622013-05-16 16:59:39 -0700287 public boolean isActionSupported(int action) {
Angus Kongb40738a2013-06-03 14:55:34 -0700288 return ((action & mSupportedActions) != 0);
Angus Kong87f9a622013-05-16 16:59:39 -0700289 }
290
291 @Override
Angus Kong750e8ec2013-05-06 10:42:28 -0700292 protected BitmapLoadTask getBitmapLoadTask(
Angus Kongb40738a2013-06-03 14:55:34 -0700293 View v, int decodeWidth, int decodeHeight) {
Angus Kong750e8ec2013-05-06 10:42:28 -0700294 return new PhotoBitmapLoadTask(v, decodeWidth, decodeHeight);
295 }
296
297 private static BitmapFactory.Options decodeDimension(String path) {
298 BitmapFactory.Options opts = new BitmapFactory.Options();
299 opts.inJustDecodeBounds = true;
300 Bitmap b = BitmapFactory.decodeFile(path, opts);
301 if (b == null) {
302 return null;
303 }
304 return opts;
305 }
306
307 private final class PhotoBitmapLoadTask extends BitmapLoadTask {
308 private int mDecodeWidth;
309 private int mDecodeHeight;
310
Angus Kongb40738a2013-06-03 14:55:34 -0700311 public PhotoBitmapLoadTask(View v, int decodeWidth, int decodeHeight) {
Angus Kong750e8ec2013-05-06 10:42:28 -0700312 super(v);
313 mDecodeWidth = decodeWidth;
314 mDecodeHeight = decodeHeight;
315 }
316
317 @Override
318 protected Bitmap doInBackground(Void... v) {
319 BitmapFactory.Options opts = null;
320 Bitmap b;
321 int sample = 1;
322 while (mDecodeWidth * sample < width
323 || mDecodeHeight * sample < height) {
324 sample *= 2;
325 }
326 opts = new BitmapFactory.Options();
327 opts.inSampleSize = sample;
328 opts.inTempStorage = DECODE_TEMP_STORAGE;
329 if (isCancelled() || !isUsing()) {
330 return null;
331 }
332 b = BitmapFactory.decodeFile(path, opts);
333 if (orientation != 0) {
334 if (isCancelled() || !isUsing()) {
335 return null;
336 }
337 Matrix m = new Matrix();
338 m.setRotate((float) orientation);
339 b = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, false);
340 }
341 return b;
342 }
343 }
344 }
345
346 static class Video extends LocalMediaData {
347 public static final int COL_ID = 0;
348 public static final int COL_TITLE = 1;
349 public static final int COL_MIME_TYPE = 2;
350 public static final int COL_DATE_TAKEN = 3;
351 public static final int COL_DATE_MODIFIED = 4;
352 public static final int COL_DATA = 5;
353 public static final int COL_WIDTH = 6;
354 public static final int COL_HEIGHT = 7;
355
Angus Kongb40738a2013-06-03 14:55:34 -0700356 static final Uri CONTENT_URI = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
357
Angus Kong87f9a622013-05-16 16:59:39 -0700358 private static final int mSupportedActions =
359 FilmStripView.ImageData.ACTION_DEMOTE
Angus Kongb40738a2013-06-03 14:55:34 -0700360 | FilmStripView.ImageData.ACTION_PROMOTE;
Angus Kong87f9a622013-05-16 16:59:39 -0700361
Angus Kong750e8ec2013-05-06 10:42:28 -0700362 static final String QUERY_ORDER = VideoColumns.DATE_TAKEN + " DESC, "
363 + VideoColumns._ID + " DESC";
364 static final String[] QUERY_PROJECTION = {
365 VideoColumns._ID, // 0, int
366 VideoColumns.TITLE, // 1, string
367 VideoColumns.MIME_TYPE, // 2, string
368 VideoColumns.DATE_TAKEN, // 3, int
369 VideoColumns.DATE_MODIFIED, // 4, int
370 VideoColumns.DATA, // 5, string
371 VideoColumns.WIDTH, // 6, int
372 VideoColumns.HEIGHT, // 7, int
373 VideoColumns.RESOLUTION
374 };
375
Angus Kongb40738a2013-06-03 14:55:34 -0700376 private Uri mPlayUri;
377
Angus Kong750e8ec2013-05-06 10:42:28 -0700378 static Video buildFromCursor(Cursor c) {
379 Video d = new Video();
380 d.id = c.getLong(COL_ID);
381 d.title = c.getString(COL_TITLE);
382 d.mimeType = c.getString(COL_MIME_TYPE);
383 d.dateTaken = c.getLong(COL_DATE_TAKEN);
384 d.dateModified = c.getLong(COL_DATE_MODIFIED);
385 d.path = c.getString(COL_DATA);
386 d.width = c.getInt(COL_WIDTH);
387 d.height = c.getInt(COL_HEIGHT);
Angus Kongb40738a2013-06-03 14:55:34 -0700388 d.mPlayUri = CONTENT_URI.buildUpon()
389 .appendPath(String.valueOf(d.id)).build();
Angus Kong750e8ec2013-05-06 10:42:28 -0700390 MediaMetadataRetriever retriever = new MediaMetadataRetriever();
391 retriever.setDataSource(d.path);
392 String rotation = retriever.extractMetadata(
393 MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
394 if (d.width == 0 || d.height == 0) {
395 d.width = Integer.parseInt(retriever.extractMetadata(
396 MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
397 d.height = Integer.parseInt(retriever.extractMetadata(
398 MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
399 }
400 retriever.release();
401 if (rotation.equals("90") || rotation.equals("270")) {
402 int b = d.width;
403 d.width = d.height;
404 d.height = b;
405 }
406 return d;
407 }
408
409 @Override
410 public String toString() {
411 return "Video:" + ",data=" + path + ",mimeType=" + mimeType
412 + "," + width + "x" + height + ",date=" + new Date(dateTaken);
413 }
414
415 @Override
416 public int getType() {
417 return TYPE_PHOTO;
418 }
419
420 @Override
Angus Kong87f9a622013-05-16 16:59:39 -0700421 public boolean isActionSupported(int action) {
422 return ((action & mSupportedActions) != 0);
423 }
424
425 @Override
Angus Kongb40738a2013-06-03 14:55:34 -0700426 public View getView(final Context c,
Angus Kong61544542013-06-03 12:48:16 -0700427 int decodeWidth, int decodeHeight, Drawable placeHolder) {
Angus Kongb40738a2013-06-03 14:55:34 -0700428 FrameLayout f = new FrameLayout(c);
429 fillViewBackground(c, f, decodeWidth, decodeHeight, placeHolder);
430 ImageView icon = new ImageView(c);
431 icon.setImageResource(R.drawable.ic_control_play);
432 icon.setScaleType(ImageView.ScaleType.CENTER);
433 icon.setLayoutParams(new FrameLayout.LayoutParams(
434 ViewGroup.LayoutParams.WRAP_CONTENT,
435 ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
436 icon.setOnClickListener(new OnClickListener() {
437 @Override
438 public void onClick(View v) {
439 Util.playVideo(c, mPlayUri, title);
440 }
441 });
442 f.addView(icon);
443 return f;
Angus Kong61544542013-06-03 12:48:16 -0700444 }
445
446 @Override
Angus Kong750e8ec2013-05-06 10:42:28 -0700447 protected BitmapLoadTask getBitmapLoadTask(
Angus Kongb40738a2013-06-03 14:55:34 -0700448 View v, int decodeWidth, int decodeHeight) {
Angus Kong750e8ec2013-05-06 10:42:28 -0700449 return new VideoBitmapLoadTask(v);
450 }
451
452 private final class VideoBitmapLoadTask extends BitmapLoadTask {
453
Angus Kongb40738a2013-06-03 14:55:34 -0700454 public VideoBitmapLoadTask(View v) {
Angus Kong750e8ec2013-05-06 10:42:28 -0700455 super(v);
456 }
457
458 @Override
459 protected Bitmap doInBackground(Void... v) {
460 if (isCancelled() || !isUsing()) {
461 return null;
462 }
463 android.media.MediaMetadataRetriever retriever = new MediaMetadataRetriever();
464 retriever.setDataSource(path);
465 byte[] data = retriever.getEmbeddedPicture();
466 Bitmap bitmap = null;
467 if (isCancelled() || !isUsing()) {
468 retriever.release();
469 return null;
470 }
471 if (data != null) {
472 bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
473 }
474 if (bitmap == null) {
475 bitmap = (Bitmap) retriever.getFrameAtTime();
476 }
477 retriever.release();
478 return bitmap;
479 }
480 }
481 }
Angus Konga06b4e22013-06-03 11:10:11 -0700482
483 /*
484 * A LocalData that does nothing but only shows a view.
485 */
486 public static class LocalViewData implements LocalData {
487 private int mWidth;
488 private int mHeight;
489 View mView;
490 private long mDateTaken;
491 private long mDateModified;
492
493 public LocalViewData(View v,
494 int width, int height,
495 int dateTaken, int dateModified) {
496 mView = v;
497 mWidth = width;
498 mHeight = height;
499 mDateTaken = dateTaken;
500 mDateModified = dateModified;
501 }
502
503 @Override
504 public long getDateTaken() {
505 return mDateTaken;
506 }
507
508 @Override
509 public long getDateModified() {
510 return mDateModified;
511 }
512
513 @Override
514 public String getTitle() {
515 return "";
516 }
517
518 @Override
519 public int getWidth() {
520 return mWidth;
521 }
522
523 @Override
524 public int getHeight() {
525 return mHeight;
526 }
527
528 @Override
529 public int getType() {
530 return FilmStripView.ImageData.TYPE_PHOTO;
531 }
532
533 @Override
534 public boolean isActionSupported(int action) {
Angus Konga06b4e22013-06-03 11:10:11 -0700535 return false;
536 }
537
538 @Override
539 public View getView(Context c, int width, int height, Drawable placeHolder) {
540 return mView;
541 }
542
543 @Override
544 public void prepare() {
545 // do nothing.
546 }
547
548 @Override
549 public void recycle() {
550 // do nothing.
551 }
552 }
Angus Kong750e8ec2013-05-06 10:42:28 -0700553}
554