Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 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 | |
Sascha Haeberling | b7639c6 | 2013-09-09 14:42:43 -0700 | [diff] [blame] | 19 | import android.app.Activity; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 20 | import android.content.ContentResolver; |
| 21 | import android.content.Context; |
| 22 | import android.database.Cursor; |
| 23 | import android.graphics.Bitmap; |
| 24 | import android.graphics.BitmapFactory; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 25 | import android.graphics.drawable.Drawable; |
Sam Judd | de3e9ab | 2014-03-17 13:07:22 -0700 | [diff] [blame] | 26 | import android.media.CamcorderProfile; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 27 | import android.net.Uri; |
Angus Kong | 8a2350a | 2013-12-16 15:02:34 -0800 | [diff] [blame] | 28 | import android.os.Bundle; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 29 | import android.provider.MediaStore; |
Sam Judd | 43bf03f | 2014-03-17 11:27:03 -0700 | [diff] [blame] | 30 | import android.view.LayoutInflater; |
Angus Kong | 2bca210 | 2014-03-11 16:27:30 -0700 | [diff] [blame^] | 31 | import android.view.Gravity; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 32 | import android.view.View; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 33 | import android.widget.ImageView; |
| 34 | |
Sam Judd | aeed91f | 2014-03-13 11:40:10 -0700 | [diff] [blame] | 35 | import com.android.camera.Storage; |
Angus Kong | 2bca210 | 2014-03-11 16:27:30 -0700 | [diff] [blame^] | 36 | import com.android.camera.debug.Log; |
Sascha Haeberling | 6f64b50 | 2013-08-14 16:23:18 -0700 | [diff] [blame] | 37 | import com.android.camera.util.CameraUtil; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 38 | import com.android.camera2.R; |
Sam Judd | 4021c89 | 2014-03-17 12:57:50 -0700 | [diff] [blame] | 39 | import com.bumptech.glide.Glide; |
| 40 | import com.bumptech.glide.presenter.target.ImageViewTarget; |
| 41 | import com.bumptech.glide.presenter.target.Target; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 42 | |
| 43 | import java.io.File; |
Sascha Haeberling | 6f64b50 | 2013-08-14 16:23:18 -0700 | [diff] [blame] | 44 | import java.text.DateFormat; |
Sam Judd | aeed91f | 2014-03-13 11:40:10 -0700 | [diff] [blame] | 45 | import java.util.ArrayList; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 46 | import java.util.Date; |
Sam Judd | aeed91f | 2014-03-13 11:40:10 -0700 | [diff] [blame] | 47 | import java.util.List; |
Sascha Haeberling | e2fb994 | 2013-08-22 13:49:46 -0700 | [diff] [blame] | 48 | import java.util.Locale; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 49 | |
| 50 | /** |
| 51 | * A base class for all the local media files. The bitmap is loaded in |
Sascha Haeberling | b0a5929 | 2013-08-19 14:02:19 -0700 | [diff] [blame] | 52 | * background thread. Subclasses should implement their own background loading |
| 53 | * thread by sub-classing BitmapLoadTask and overriding doInBackground() to |
| 54 | * return a bitmap. |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 55 | */ |
| 56 | public abstract class LocalMediaData implements LocalData { |
Sam Judd | aeed91f | 2014-03-13 11:40:10 -0700 | [diff] [blame] | 57 | /** The minimum id to use to query for all media at a given media store uri */ |
| 58 | static final int QUERY_ALL_MEDIA_ID = -1; |
| 59 | private static final String CAMERA_PATH = Storage.DIRECTORY + "%"; |
| 60 | private static final String SELECT_BY_PATH = MediaStore.MediaColumns.DATA + " LIKE ?"; |
| 61 | |
Angus Kong | 3250987 | 2013-10-02 16:53:40 -0700 | [diff] [blame] | 62 | protected final long mContentId; |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 63 | protected final String mTitle; |
| 64 | protected final String mMimeType; |
| 65 | protected final long mDateTakenInSeconds; |
| 66 | protected final long mDateModifiedInSeconds; |
| 67 | protected final String mPath; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 68 | // width and height should be adjusted according to orientation. |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 69 | protected final int mWidth; |
| 70 | protected final int mHeight; |
| 71 | protected final long mSizeInBytes; |
| 72 | protected final double mLatitude; |
| 73 | protected final double mLongitude; |
Angus Kong | 8a2350a | 2013-12-16 15:02:34 -0800 | [diff] [blame] | 74 | protected final Bundle mMetaData; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 75 | |
Angus Kong | c27d21b | 2013-08-12 15:03:45 -0700 | [diff] [blame] | 76 | /** |
Sascha Haeberling | b0a5929 | 2013-08-19 14:02:19 -0700 | [diff] [blame] | 77 | * Used for thumbnail loading optimization. True if this data has a |
| 78 | * corresponding visible view. |
Angus Kong | c27d21b | 2013-08-12 15:03:45 -0700 | [diff] [blame] | 79 | */ |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 80 | protected Boolean mUsing = false; |
| 81 | |
Sascha Haeberling | 280fd3e | 2013-11-21 13:52:15 -0800 | [diff] [blame] | 82 | public LocalMediaData(long contentId, String title, String mimeType, |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 83 | long dateTakenInSeconds, long dateModifiedInSeconds, String path, |
| 84 | int width, int height, long sizeInBytes, double latitude, |
| 85 | double longitude) { |
Angus Kong | 3250987 | 2013-10-02 16:53:40 -0700 | [diff] [blame] | 86 | mContentId = contentId; |
Sam Judd | 43bf03f | 2014-03-17 11:27:03 -0700 | [diff] [blame] | 87 | mTitle = title; |
| 88 | mMimeType = mimeType; |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 89 | mDateTakenInSeconds = dateTakenInSeconds; |
| 90 | mDateModifiedInSeconds = dateModifiedInSeconds; |
Sam Judd | 43bf03f | 2014-03-17 11:27:03 -0700 | [diff] [blame] | 91 | mPath = path; |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 92 | mWidth = width; |
| 93 | mHeight = height; |
| 94 | mSizeInBytes = sizeInBytes; |
| 95 | mLatitude = latitude; |
| 96 | mLongitude = longitude; |
Angus Kong | 8a2350a | 2013-12-16 15:02:34 -0800 | [diff] [blame] | 97 | mMetaData = new Bundle(); |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 98 | } |
| 99 | |
Sam Judd | aeed91f | 2014-03-13 11:40:10 -0700 | [diff] [blame] | 100 | private interface CursorToLocalData { |
| 101 | public LocalData build(Cursor cursor); |
| 102 | } |
| 103 | |
| 104 | private static List<LocalData> queryLocalMediaData(ContentResolver contentResolver, |
| 105 | Uri contentUri, String[] projection, long minimumId, String orderBy, |
| 106 | CursorToLocalData builder) { |
| 107 | String selection = SELECT_BY_PATH + " AND " + MediaStore.MediaColumns._ID + " > ?"; |
| 108 | String[] selectionArgs = new String[] { CAMERA_PATH, Long.toString(minimumId) }; |
| 109 | |
| 110 | Cursor cursor = contentResolver.query(contentUri, projection, |
| 111 | selection, selectionArgs, orderBy); |
| 112 | List<LocalData> result = new ArrayList<LocalData>(); |
| 113 | if (cursor != null) { |
| 114 | while (cursor.moveToNext()) { |
| 115 | LocalData data = builder.build(cursor); |
| 116 | if (data != null) { |
| 117 | result.add(data); |
| 118 | } else { |
| 119 | final int dataIndex = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); |
Angus Kong | 2bca210 | 2014-03-11 16:27:30 -0700 | [diff] [blame^] | 120 | Log.e(TAG, "Error loading data:" + cursor.getString(dataIndex)); |
Sam Judd | aeed91f | 2014-03-13 11:40:10 -0700 | [diff] [blame] | 121 | } |
| 122 | } |
Sam Judd | de3e9ab | 2014-03-17 13:07:22 -0700 | [diff] [blame] | 123 | |
| 124 | cursor.close(); |
Sam Judd | aeed91f | 2014-03-13 11:40:10 -0700 | [diff] [blame] | 125 | } |
| 126 | return result; |
| 127 | } |
| 128 | |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 129 | @Override |
| 130 | public long getDateTaken() { |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 131 | return mDateTakenInSeconds; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 132 | } |
| 133 | |
| 134 | @Override |
| 135 | public long getDateModified() { |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 136 | return mDateModifiedInSeconds; |
| 137 | } |
| 138 | |
| 139 | @Override |
Angus Kong | 3250987 | 2013-10-02 16:53:40 -0700 | [diff] [blame] | 140 | public long getContentId() { |
| 141 | return mContentId; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 142 | } |
| 143 | |
| 144 | @Override |
| 145 | public String getTitle() { |
Sam Judd | 4021c89 | 2014-03-17 12:57:50 -0700 | [diff] [blame] | 146 | return mTitle; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 147 | } |
| 148 | |
| 149 | @Override |
| 150 | public int getWidth() { |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 151 | return mWidth; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 152 | } |
| 153 | |
| 154 | @Override |
| 155 | public int getHeight() { |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 156 | return mHeight; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 157 | } |
| 158 | |
| 159 | @Override |
Angus Kong | c195e7a | 2014-02-20 16:56:37 -0800 | [diff] [blame] | 160 | public int getRotation() { |
Doris Liu | 87fc5e1 | 2013-09-16 13:38:24 -0700 | [diff] [blame] | 161 | return 0; |
| 162 | } |
| 163 | |
| 164 | @Override |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 165 | public String getPath() { |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 166 | return mPath; |
| 167 | } |
| 168 | |
| 169 | @Override |
| 170 | public long getSizeInBytes() { |
| 171 | return mSizeInBytes; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 172 | } |
| 173 | |
| 174 | @Override |
| 175 | public boolean isUIActionSupported(int action) { |
| 176 | return false; |
| 177 | } |
| 178 | |
| 179 | @Override |
| 180 | public boolean isDataActionSupported(int action) { |
| 181 | return false; |
| 182 | } |
| 183 | |
| 184 | @Override |
Angus Kong | 8a2350a | 2013-12-16 15:02:34 -0800 | [diff] [blame] | 185 | public boolean delete(Context context) { |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 186 | File f = new File(mPath); |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 187 | return f.delete(); |
| 188 | } |
| 189 | |
| 190 | @Override |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 191 | public void onFullScreen(boolean fullScreen) { |
| 192 | // do nothing. |
| 193 | } |
| 194 | |
| 195 | @Override |
| 196 | public boolean canSwipeInFullScreen() { |
| 197 | return true; |
| 198 | } |
| 199 | |
Angus Kong | 8a2350a | 2013-12-16 15:02:34 -0800 | [diff] [blame] | 200 | protected ImageView fillImageView(Context context, ImageView v, |
Sam Judd | 4021c89 | 2014-03-17 12:57:50 -0700 | [diff] [blame] | 201 | int decodeWidth, int decodeHeight, int placeHolderResourceId, |
Sascha Haeberling | 14ff6c8 | 2013-12-13 13:29:58 -0800 | [diff] [blame] | 202 | LocalDataAdapter adapter, boolean isInProgress) { |
Sam Judd | 4021c89 | 2014-03-17 12:57:50 -0700 | [diff] [blame] | 203 | |
| 204 | SizedImageViewTarget target = (SizedImageViewTarget) v.getTag(); |
| 205 | if (target != null) { |
| 206 | target = new SizedImageViewTarget(v); |
| 207 | v.setTag(target); |
Andy Huibers | caca8c7 | 2013-12-13 15:53:43 -0800 | [diff] [blame] | 208 | } |
| 209 | |
Sam Judd | 4021c89 | 2014-03-17 12:57:50 -0700 | [diff] [blame] | 210 | Glide.with(context) |
| 211 | .load(mPath) |
| 212 | .fitCenter() |
| 213 | .placeholder(placeHolderResourceId) |
| 214 | .into(target); |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 215 | |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 216 | return v; |
| 217 | } |
| 218 | |
| 219 | @Override |
Sam Judd | 4021c89 | 2014-03-17 12:57:50 -0700 | [diff] [blame] | 220 | public View getView(Context context, View recycled, int decodeWidth, int decodeHeight, |
| 221 | int placeHolderResourceId, LocalDataAdapter adapter, boolean isInProgress) { |
Sam Judd | 43bf03f | 2014-03-17 11:27:03 -0700 | [diff] [blame] | 222 | final ImageView imageView; |
| 223 | if (recycled != null) { |
| 224 | imageView = (ImageView) recycled; |
| 225 | } else { |
| 226 | imageView = new ImageView(context); |
| 227 | } |
| 228 | |
| 229 | return fillImageView(context, imageView, decodeWidth, decodeHeight, |
Sam Judd | 4021c89 | 2014-03-17 12:57:50 -0700 | [diff] [blame] | 230 | placeHolderResourceId, adapter, isInProgress); |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 231 | } |
| 232 | |
| 233 | @Override |
Sam Judd | 4021c89 | 2014-03-17 12:57:50 -0700 | [diff] [blame] | 234 | public void loadFullImage(Context context, int width, int height, View view, |
| 235 | LocalDataAdapter adapter) { |
Andy Huibers | caca8c7 | 2013-12-13 15:53:43 -0800 | [diff] [blame] | 236 | // Default is do nothing. |
| 237 | // Can be implemented by sub-classes. |
| 238 | } |
| 239 | |
| 240 | @Override |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 241 | public void prepare() { |
| 242 | synchronized (mUsing) { |
| 243 | mUsing = true; |
| 244 | } |
| 245 | } |
| 246 | |
| 247 | @Override |
Sam Judd | 4021c89 | 2014-03-17 12:57:50 -0700 | [diff] [blame] | 248 | public void recycle(View view) { |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 249 | synchronized (mUsing) { |
| 250 | mUsing = false; |
| 251 | } |
| 252 | } |
| 253 | |
Sascha Haeberling | fae11a1 | 2013-08-15 14:29:49 -0700 | [diff] [blame] | 254 | @Override |
| 255 | public double[] getLatLong() { |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 256 | if (mLatitude == 0 && mLongitude == 0) { |
Sascha Haeberling | fae11a1 | 2013-08-15 14:29:49 -0700 | [diff] [blame] | 257 | return null; |
| 258 | } |
| 259 | return new double[] { |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 260 | mLatitude, mLongitude |
Sascha Haeberling | fae11a1 | 2013-08-15 14:29:49 -0700 | [diff] [blame] | 261 | }; |
| 262 | } |
| 263 | |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 264 | protected boolean isUsing() { |
| 265 | synchronized (mUsing) { |
| 266 | return mUsing; |
| 267 | } |
| 268 | } |
| 269 | |
| 270 | @Override |
Sascha Haeberling | 88ef766 | 2013-08-15 17:19:22 -0700 | [diff] [blame] | 271 | public String getMimeType() { |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 272 | return mMimeType; |
Sascha Haeberling | 88ef766 | 2013-08-15 17:19:22 -0700 | [diff] [blame] | 273 | } |
| 274 | |
| 275 | @Override |
Sascha Haeberling | b0a5929 | 2013-08-19 14:02:19 -0700 | [diff] [blame] | 276 | public MediaDetails getMediaDetails(Context context) { |
Sascha Haeberling | e2fb994 | 2013-08-22 13:49:46 -0700 | [diff] [blame] | 277 | DateFormat dateFormatter = DateFormat.getDateTimeInstance(); |
Sascha Haeberling | b0a5929 | 2013-08-19 14:02:19 -0700 | [diff] [blame] | 278 | MediaDetails mediaDetails = new MediaDetails(); |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 279 | mediaDetails.addDetail(MediaDetails.INDEX_TITLE, mTitle); |
| 280 | mediaDetails.addDetail(MediaDetails.INDEX_WIDTH, mWidth); |
| 281 | mediaDetails.addDetail(MediaDetails.INDEX_HEIGHT, mHeight); |
| 282 | mediaDetails.addDetail(MediaDetails.INDEX_PATH, mPath); |
Sascha Haeberling | b0a5929 | 2013-08-19 14:02:19 -0700 | [diff] [blame] | 283 | mediaDetails.addDetail(MediaDetails.INDEX_DATETIME, |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 284 | dateFormatter.format(new Date(mDateModifiedInSeconds * 1000))); |
| 285 | if (mSizeInBytes > 0) { |
| 286 | mediaDetails.addDetail(MediaDetails.INDEX_SIZE, mSizeInBytes); |
Sascha Haeberling | b0a5929 | 2013-08-19 14:02:19 -0700 | [diff] [blame] | 287 | } |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 288 | if (mLatitude != 0 && mLongitude != 0) { |
| 289 | String locationString = String.format(Locale.getDefault(), "%f, %f", mLatitude, |
| 290 | mLongitude); |
Sascha Haeberling | e2fb994 | 2013-08-22 13:49:46 -0700 | [diff] [blame] | 291 | mediaDetails.addDetail(MediaDetails.INDEX_LOCATION, locationString); |
Sascha Haeberling | b0a5929 | 2013-08-19 14:02:19 -0700 | [diff] [blame] | 292 | } |
| 293 | return mediaDetails; |
| 294 | } |
| 295 | |
| 296 | @Override |
Angus Kong | c27d21b | 2013-08-12 15:03:45 -0700 | [diff] [blame] | 297 | public abstract int getViewType(); |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 298 | |
Angus Kong | 8a2350a | 2013-12-16 15:02:34 -0800 | [diff] [blame] | 299 | @Override |
| 300 | public Bundle getMetadata() { |
| 301 | return mMetaData; |
| 302 | } |
| 303 | |
Angus Kong | 56102c6 | 2013-12-19 12:16:54 -0800 | [diff] [blame] | 304 | @Override |
| 305 | public boolean isMetadataUpdated() { |
Sam Judd | de3e9ab | 2014-03-17 13:07:22 -0700 | [diff] [blame] | 306 | return MetadataLoader.isMetadataCached(this); |
Angus Kong | 56102c6 | 2013-12-19 12:16:54 -0800 | [diff] [blame] | 307 | } |
| 308 | |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 309 | public static final class PhotoData extends LocalMediaData { |
Angus Kong | 2bca210 | 2014-03-11 16:27:30 -0700 | [diff] [blame^] | 310 | private static final Log.Tag TAG = new Log.Tag("PhotoData"); |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 311 | |
| 312 | public static final int COL_ID = 0; |
| 313 | public static final int COL_TITLE = 1; |
| 314 | public static final int COL_MIME_TYPE = 2; |
| 315 | public static final int COL_DATE_TAKEN = 3; |
| 316 | public static final int COL_DATE_MODIFIED = 4; |
| 317 | public static final int COL_DATA = 5; |
| 318 | public static final int COL_ORIENTATION = 6; |
| 319 | public static final int COL_WIDTH = 7; |
| 320 | public static final int COL_HEIGHT = 8; |
Sascha Haeberling | 6f64b50 | 2013-08-14 16:23:18 -0700 | [diff] [blame] | 321 | public static final int COL_SIZE = 9; |
Sascha Haeberling | fae11a1 | 2013-08-15 14:29:49 -0700 | [diff] [blame] | 322 | public static final int COL_LATITUDE = 10; |
| 323 | public static final int COL_LONGITUDE = 11; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 324 | |
Andy Huibers | caca8c7 | 2013-12-13 15:53:43 -0800 | [diff] [blame] | 325 | // GL max texture size: keep bitmaps below this value. |
| 326 | private static final int MAXIMUM_TEXTURE_SIZE = 2048; |
Andy Huibers | caca8c7 | 2013-12-13 15:53:43 -0800 | [diff] [blame] | 327 | |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 328 | static final Uri CONTENT_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; |
| 329 | |
Sam Judd | aeed91f | 2014-03-13 11:40:10 -0700 | [diff] [blame] | 330 | private static final String QUERY_ORDER = MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC, " |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 331 | + MediaStore.Images.ImageColumns._ID + " DESC"; |
| 332 | /** |
| 333 | * These values should be kept in sync with column IDs (COL_*) above. |
| 334 | */ |
Sam Judd | aeed91f | 2014-03-13 11:40:10 -0700 | [diff] [blame] | 335 | private static final String[] QUERY_PROJECTION = { |
Sascha Haeberling | b0a5929 | 2013-08-19 14:02:19 -0700 | [diff] [blame] | 336 | MediaStore.Images.ImageColumns._ID, // 0, int |
| 337 | MediaStore.Images.ImageColumns.TITLE, // 1, string |
| 338 | MediaStore.Images.ImageColumns.MIME_TYPE, // 2, string |
| 339 | MediaStore.Images.ImageColumns.DATE_TAKEN, // 3, int |
| 340 | MediaStore.Images.ImageColumns.DATE_MODIFIED, // 4, int |
| 341 | MediaStore.Images.ImageColumns.DATA, // 5, string |
| 342 | MediaStore.Images.ImageColumns.ORIENTATION, // 6, int, 0, 90, 180, 270 |
| 343 | MediaStore.Images.ImageColumns.WIDTH, // 7, int |
| 344 | MediaStore.Images.ImageColumns.HEIGHT, // 8, int |
| 345 | MediaStore.Images.ImageColumns.SIZE, // 9, long |
| 346 | MediaStore.Images.ImageColumns.LATITUDE, // 10, double |
| 347 | MediaStore.Images.ImageColumns.LONGITUDE // 11, double |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 348 | }; |
| 349 | |
Angus Kong | e0aff89 | 2013-12-11 20:51:01 -0800 | [diff] [blame] | 350 | private static final int mSupportedUIActions = ACTION_DEMOTE | ACTION_PROMOTE | ACTION_ZOOM; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 351 | private static final int mSupportedDataActions = |
Angus Kong | e0aff89 | 2013-12-11 20:51:01 -0800 | [diff] [blame] | 352 | DATA_ACTION_DELETE | DATA_ACTION_EDIT | DATA_ACTION_SHARE; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 353 | |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 354 | /** from MediaStore, can only be 0, 90, 180, 270 */ |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 355 | private final int mOrientation; |
Sam Judd | 4021c89 | 2014-03-17 12:57:50 -0700 | [diff] [blame] | 356 | /** @see #getSignature() */ |
| 357 | private final String mSignature; |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 358 | |
Angus Kong | 571a8c3 | 2014-03-13 12:53:03 -0700 | [diff] [blame] | 359 | public static LocalData fromContentUri(ContentResolver cr, Uri contentUri) { |
| 360 | List<LocalData> newPhotos = query(cr, contentUri, QUERY_ALL_MEDIA_ID); |
| 361 | if (newPhotos.isEmpty()) { |
| 362 | return null; |
| 363 | } |
| 364 | return newPhotos.get(0); |
| 365 | } |
| 366 | |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 367 | public PhotoData(long id, String title, String mimeType, |
| 368 | long dateTakenInSeconds, long dateModifiedInSeconds, |
| 369 | String path, int orientation, int width, int height, |
| 370 | long sizeInBytes, double latitude, double longitude) { |
| 371 | super(id, title, mimeType, dateTakenInSeconds, dateModifiedInSeconds, |
| 372 | path, width, height, sizeInBytes, latitude, longitude); |
| 373 | mOrientation = orientation; |
Sam Judd | 4021c89 | 2014-03-17 12:57:50 -0700 | [diff] [blame] | 374 | mSignature = mimeType + orientation + dateModifiedInSeconds; |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 375 | } |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 376 | |
Sam Judd | aeed91f | 2014-03-13 11:40:10 -0700 | [diff] [blame] | 377 | static List<LocalData> query(ContentResolver cr, Uri uri, long lastId) { |
| 378 | return queryLocalMediaData(cr, uri, QUERY_PROJECTION, lastId, QUERY_ORDER, |
| 379 | new PhotoDataBuilder()); |
| 380 | } |
| 381 | |
| 382 | private static PhotoData buildFromCursor(Cursor c) { |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 383 | long id = c.getLong(COL_ID); |
| 384 | String title = c.getString(COL_TITLE); |
| 385 | String mimeType = c.getString(COL_MIME_TYPE); |
| 386 | long dateTakenInSeconds = c.getLong(COL_DATE_TAKEN); |
| 387 | long dateModifiedInSeconds = c.getLong(COL_DATE_MODIFIED); |
| 388 | String path = c.getString(COL_DATA); |
| 389 | int orientation = c.getInt(COL_ORIENTATION); |
| 390 | int width = c.getInt(COL_WIDTH); |
| 391 | int height = c.getInt(COL_HEIGHT); |
| 392 | if (width <= 0 || height <= 0) { |
Angus Kong | 62a622b | 2013-08-30 15:10:38 -0700 | [diff] [blame] | 393 | Log.w(TAG, "Zero dimension in ContentResolver for " |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 394 | + path + ":" + width + "x" + height); |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 395 | BitmapFactory.Options opts = new BitmapFactory.Options(); |
| 396 | opts.inJustDecodeBounds = true; |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 397 | BitmapFactory.decodeFile(path, opts); |
Angus Kong | 62a622b | 2013-08-30 15:10:38 -0700 | [diff] [blame] | 398 | if (opts.outWidth > 0 && opts.outHeight > 0) { |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 399 | width = opts.outWidth; |
| 400 | height = opts.outHeight; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 401 | } else { |
Angus Kong | 62a622b | 2013-08-30 15:10:38 -0700 | [diff] [blame] | 402 | Log.w(TAG, "Dimension decode failed for " + path); |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 403 | Bitmap b = BitmapFactory.decodeFile(path); |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 404 | if (b == null) { |
Angus Kong | 3250987 | 2013-10-02 16:53:40 -0700 | [diff] [blame] | 405 | Log.w(TAG, "PhotoData skipped." |
Angus Kong | 62a622b | 2013-08-30 15:10:38 -0700 | [diff] [blame] | 406 | + " Decoding " + path + "failed."); |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 407 | return null; |
| 408 | } |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 409 | width = b.getWidth(); |
| 410 | height = b.getHeight(); |
Angus Kong | 62a622b | 2013-08-30 15:10:38 -0700 | [diff] [blame] | 411 | if (width == 0 || height == 0) { |
| 412 | Log.w(TAG, "PhotoData skipped. Bitmap size 0 for " + path); |
| 413 | return null; |
| 414 | } |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 415 | } |
| 416 | } |
ztenghui | 06578b5 | 2013-10-07 14:54:55 -0700 | [diff] [blame] | 417 | |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 418 | long sizeInBytes = c.getLong(COL_SIZE); |
| 419 | double latitude = c.getDouble(COL_LATITUDE); |
| 420 | double longitude = c.getDouble(COL_LONGITUDE); |
| 421 | PhotoData result = new PhotoData(id, title, mimeType, dateTakenInSeconds, |
| 422 | dateModifiedInSeconds, path, orientation, width, height, |
| 423 | sizeInBytes, latitude, longitude); |
| 424 | return result; |
| 425 | } |
| 426 | |
Doris Liu | 87fc5e1 | 2013-09-16 13:38:24 -0700 | [diff] [blame] | 427 | @Override |
Angus Kong | c195e7a | 2014-02-20 16:56:37 -0800 | [diff] [blame] | 428 | public int getRotation() { |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 429 | return mOrientation; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 430 | } |
| 431 | |
| 432 | @Override |
| 433 | public String toString() { |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 434 | return "Photo:" + ",data=" + mPath + ",mimeType=" + mMimeType |
| 435 | + "," + mWidth + "x" + mHeight + ",orientation=" + mOrientation |
| 436 | + ",date=" + new Date(mDateTakenInSeconds); |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 437 | } |
| 438 | |
| 439 | @Override |
Angus Kong | c27d21b | 2013-08-12 15:03:45 -0700 | [diff] [blame] | 440 | public int getViewType() { |
Angus Kong | b21215a | 2013-09-20 18:41:46 -0700 | [diff] [blame] | 441 | return VIEW_TYPE_REMOVABLE; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 442 | } |
| 443 | |
| 444 | @Override |
| 445 | public boolean isUIActionSupported(int action) { |
| 446 | return ((action & mSupportedUIActions) == action); |
| 447 | } |
| 448 | |
| 449 | @Override |
| 450 | public boolean isDataActionSupported(int action) { |
| 451 | return ((action & mSupportedDataActions) == action); |
| 452 | } |
| 453 | |
| 454 | @Override |
Angus Kong | 8a2350a | 2013-12-16 15:02:34 -0800 | [diff] [blame] | 455 | public boolean delete(Context context) { |
| 456 | ContentResolver cr = context.getContentResolver(); |
Angus Kong | 3250987 | 2013-10-02 16:53:40 -0700 | [diff] [blame] | 457 | cr.delete(CONTENT_URI, MediaStore.Images.ImageColumns._ID + "=" + mContentId, null); |
Angus Kong | 8a2350a | 2013-12-16 15:02:34 -0800 | [diff] [blame] | 458 | return super.delete(context); |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 459 | } |
| 460 | |
| 461 | @Override |
Angus Kong | 571a8c3 | 2014-03-13 12:53:03 -0700 | [diff] [blame] | 462 | public Uri getUri() { |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 463 | Uri baseUri = CONTENT_URI; |
Angus Kong | 3250987 | 2013-10-02 16:53:40 -0700 | [diff] [blame] | 464 | return baseUri.buildUpon().appendPath(String.valueOf(mContentId)).build(); |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 465 | } |
| 466 | |
| 467 | @Override |
Sascha Haeberling | 6f64b50 | 2013-08-14 16:23:18 -0700 | [diff] [blame] | 468 | public MediaDetails getMediaDetails(Context context) { |
Sascha Haeberling | b0a5929 | 2013-08-19 14:02:19 -0700 | [diff] [blame] | 469 | MediaDetails mediaDetails = super.getMediaDetails(context); |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 470 | MediaDetails.extractExifInfo(mediaDetails, mPath); |
Sascha Haeberling | bfd919a | 2013-09-26 16:01:23 -0700 | [diff] [blame] | 471 | mediaDetails.addDetail(MediaDetails.INDEX_ORIENTATION, mOrientation); |
Sascha Haeberling | 6f64b50 | 2013-08-14 16:23:18 -0700 | [diff] [blame] | 472 | return mediaDetails; |
| 473 | } |
| 474 | |
| 475 | @Override |
Sascha Haeberling | fae11a1 | 2013-08-15 14:29:49 -0700 | [diff] [blame] | 476 | public int getLocalDataType() { |
Angus Kong | c27d21b | 2013-08-12 15:03:45 -0700 | [diff] [blame] | 477 | return LOCAL_IMAGE; |
| 478 | } |
| 479 | |
| 480 | @Override |
Angus Kong | 8a2350a | 2013-12-16 15:02:34 -0800 | [diff] [blame] | 481 | public LocalData refresh(Context context) { |
Sam Judd | aeed91f | 2014-03-13 11:40:10 -0700 | [diff] [blame] | 482 | PhotoData newData = null; |
Angus Kong | 571a8c3 | 2014-03-13 12:53:03 -0700 | [diff] [blame] | 483 | Cursor c = context.getContentResolver().query(getUri(), QUERY_PROJECTION, null, |
Angus Kong | 8a2350a | 2013-12-16 15:02:34 -0800 | [diff] [blame] | 484 | null, null); |
Sam Judd | aeed91f | 2014-03-13 11:40:10 -0700 | [diff] [blame] | 485 | if (c != null) { |
| 486 | if (c.moveToFirst()) { |
| 487 | newData = buildFromCursor(c); |
| 488 | } |
| 489 | c.close(); |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 490 | } |
Sam Judd | aeed91f | 2014-03-13 11:40:10 -0700 | [diff] [blame] | 491 | |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 492 | return newData; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 493 | } |
| 494 | |
| 495 | @Override |
Sam Judd | 4021c89 | 2014-03-17 12:57:50 -0700 | [diff] [blame] | 496 | public String getSignature() { |
| 497 | return mSignature; |
| 498 | } |
| 499 | |
| 500 | @Override |
| 501 | protected ImageView fillImageView(Context context, final ImageView v, final int decodeWidth, |
| 502 | final int decodeHeight, int placeHolderResourceId, LocalDataAdapter adapter, |
| 503 | boolean isInProgress) { |
| 504 | loadImage(context, v, decodeWidth, decodeHeight, placeHolderResourceId, false); |
| 505 | return v; |
| 506 | } |
| 507 | |
| 508 | private void loadImage(Context context, ImageView imageView, int decodeWidth, |
| 509 | int decodeHeight, int placeHolderResourceId, boolean full) { |
| 510 | ThumbTarget thumbTarget = (ThumbTarget) imageView.getTag(); |
| 511 | if (thumbTarget == null) { |
| 512 | thumbTarget = new ThumbTarget(imageView); |
| 513 | imageView.setTag(thumbTarget); |
| 514 | } |
| 515 | // Make sure we've reset all state related to showing thumb and full whenever |
| 516 | // we load a thumbnail because loading a thumbnail only happens when we're changing |
| 517 | // images. |
| 518 | if (!full) { |
| 519 | thumbTarget.clearTargets(); |
| 520 | } |
| 521 | |
| 522 | Glide.with(context) |
| 523 | .using(new ImageModelLoader(context)) |
| 524 | .load(this) |
| 525 | .placeholder(placeHolderResourceId) |
| 526 | .fitCenter() |
| 527 | .into(thumbTarget.getTarget(decodeWidth, decodeHeight, full)); |
| 528 | } |
| 529 | |
| 530 | @Override |
| 531 | public void recycle(View view) { |
| 532 | super.recycle(view); |
| 533 | if (view != null) { |
| 534 | ThumbTarget thumbTarget = (ThumbTarget) view.getTag(); |
| 535 | thumbTarget.clearTargets(); |
| 536 | } |
| 537 | } |
| 538 | |
| 539 | @Override |
Sam Judd | 43bf03f | 2014-03-17 11:27:03 -0700 | [diff] [blame] | 540 | public LocalDataViewType getItemViewType() { |
| 541 | return LocalDataViewType.PHOTO; |
| 542 | } |
| 543 | |
| 544 | @Override |
Sam Judd | 4021c89 | 2014-03-17 12:57:50 -0700 | [diff] [blame] | 545 | public void loadFullImage(Context context, int w, int h, View v, LocalDataAdapter adapter) |
Andy Huibers | caca8c7 | 2013-12-13 15:53:43 -0800 | [diff] [blame] | 546 | { |
Sam Judd | 4021c89 | 2014-03-17 12:57:50 -0700 | [diff] [blame] | 547 | loadImage(context, (ImageView) v, w, h, 0, true); |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 548 | } |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 549 | |
Sam Judd | aeed91f | 2014-03-13 11:40:10 -0700 | [diff] [blame] | 550 | private static class PhotoDataBuilder implements CursorToLocalData { |
| 551 | @Override |
| 552 | public PhotoData build(Cursor cursor) { |
| 553 | return LocalMediaData.PhotoData.buildFromCursor(cursor); |
| 554 | } |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 555 | } |
Sam Judd | 4021c89 | 2014-03-17 12:57:50 -0700 | [diff] [blame] | 556 | |
| 557 | /** |
| 558 | * In the filmstrip we want to load a small version of an image and then load a |
| 559 | * larger version when we switch to full screen mode. This class manages |
| 560 | * that transition and loading process by keeping one target for the smaller thumbnail |
| 561 | * and a second target for the larger version. |
| 562 | */ |
| 563 | private static class ThumbTarget { |
| 564 | private final SizedImageViewTarget mThumbTarget; |
| 565 | private final SizedImageViewTarget mFullTarget; |
| 566 | private boolean fullLoaded = false; |
| 567 | |
| 568 | public ThumbTarget(ImageView imageView) { |
| 569 | mThumbTarget = new SizedImageViewTarget(imageView) { |
| 570 | @Override |
| 571 | public void onImageReady(Bitmap bitmap) { |
| 572 | // If we manage to load the thumb after the full, we don't |
| 573 | // want to replace the higher quality full with the thumb. |
| 574 | if (!fullLoaded) { |
| 575 | super.onImageReady(bitmap); |
| 576 | } |
| 577 | } |
| 578 | }; |
| 579 | |
| 580 | mFullTarget = new SizedImageViewTarget(imageView) { |
| 581 | |
| 582 | @Override |
| 583 | public void onImageReady(Bitmap bitmap) { |
| 584 | // When the full is loaded, we no longer need the thumb. |
| 585 | fullLoaded = true; |
| 586 | Glide.cancel(mThumbTarget); |
| 587 | super.onImageReady(bitmap); |
| 588 | } |
| 589 | |
| 590 | @Override |
| 591 | public void setPlaceholder(Drawable placeholder) { |
| 592 | // We always load the thumb first which will set the placeholder. |
| 593 | // If we were to set the placeholder here too, instead of showing |
| 594 | // the thumb while we load the full, we will instead revert back |
| 595 | // to the placeholder. |
| 596 | } |
| 597 | }; |
| 598 | } |
| 599 | |
| 600 | public Target getTarget(int width, int height, boolean full) { |
| 601 | final SizedImageViewTarget result = full ? mFullTarget : mThumbTarget; |
| 602 | // Limit the target size so we don't load a bitmap larger than the max size we |
| 603 | // can display. |
| 604 | width = Math.min(width, MAXIMUM_TEXTURE_SIZE); |
| 605 | height = Math.min(height, MAXIMUM_TEXTURE_SIZE); |
| 606 | |
| 607 | result.setSize(width, height); |
| 608 | return result; |
| 609 | } |
| 610 | |
| 611 | public void clearTargets() { |
| 612 | fullLoaded = false; |
| 613 | Glide.cancel(mThumbTarget); |
| 614 | Glide.cancel(mFullTarget); |
| 615 | } |
| 616 | } |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 617 | } |
| 618 | |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 619 | public static final class VideoData extends LocalMediaData { |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 620 | public static final int COL_ID = 0; |
| 621 | public static final int COL_TITLE = 1; |
| 622 | public static final int COL_MIME_TYPE = 2; |
| 623 | public static final int COL_DATE_TAKEN = 3; |
| 624 | public static final int COL_DATE_MODIFIED = 4; |
| 625 | public static final int COL_DATA = 5; |
| 626 | public static final int COL_WIDTH = 6; |
| 627 | public static final int COL_HEIGHT = 7; |
Sam Judd | 43bf03f | 2014-03-17 11:27:03 -0700 | [diff] [blame] | 628 | public static final int COL_SIZE = 8; |
| 629 | public static final int COL_LATITUDE = 9; |
| 630 | public static final int COL_LONGITUDE = 10; |
| 631 | public static final int COL_DURATION = 11; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 632 | |
| 633 | static final Uri CONTENT_URI = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; |
| 634 | |
Angus Kong | e0aff89 | 2013-12-11 20:51:01 -0800 | [diff] [blame] | 635 | private static final int mSupportedUIActions = ACTION_DEMOTE | ACTION_PROMOTE; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 636 | private static final int mSupportedDataActions = |
Angus Kong | e0aff89 | 2013-12-11 20:51:01 -0800 | [diff] [blame] | 637 | DATA_ACTION_DELETE | DATA_ACTION_PLAY | DATA_ACTION_SHARE; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 638 | |
Sam Judd | aeed91f | 2014-03-13 11:40:10 -0700 | [diff] [blame] | 639 | private static final String QUERY_ORDER = MediaStore.Video.VideoColumns.DATE_TAKEN |
| 640 | + " DESC, " + MediaStore.Video.VideoColumns._ID + " DESC"; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 641 | /** |
| 642 | * These values should be kept in sync with column IDs (COL_*) above. |
| 643 | */ |
Sam Judd | aeed91f | 2014-03-13 11:40:10 -0700 | [diff] [blame] | 644 | private static final String[] QUERY_PROJECTION = { |
Sascha Haeberling | b0a5929 | 2013-08-19 14:02:19 -0700 | [diff] [blame] | 645 | MediaStore.Video.VideoColumns._ID, // 0, int |
| 646 | MediaStore.Video.VideoColumns.TITLE, // 1, string |
| 647 | MediaStore.Video.VideoColumns.MIME_TYPE, // 2, string |
| 648 | MediaStore.Video.VideoColumns.DATE_TAKEN, // 3, int |
| 649 | MediaStore.Video.VideoColumns.DATE_MODIFIED, // 4, int |
| 650 | MediaStore.Video.VideoColumns.DATA, // 5, string |
| 651 | MediaStore.Video.VideoColumns.WIDTH, // 6, int |
| 652 | MediaStore.Video.VideoColumns.HEIGHT, // 7, int |
Sam Judd | 43bf03f | 2014-03-17 11:27:03 -0700 | [diff] [blame] | 653 | MediaStore.Video.VideoColumns.SIZE, // 8 long |
| 654 | MediaStore.Video.VideoColumns.LATITUDE, // 9 double |
| 655 | MediaStore.Video.VideoColumns.LONGITUDE, // 10 double |
| 656 | MediaStore.Video.VideoColumns.DURATION // 11 long |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 657 | }; |
| 658 | |
Sascha Haeberling | b0a5929 | 2013-08-19 14:02:19 -0700 | [diff] [blame] | 659 | /** The duration in milliseconds. */ |
Sascha Haeberling | 280fd3e | 2013-11-21 13:52:15 -0800 | [diff] [blame] | 660 | private final long mDurationInSeconds; |
Sam Judd | 4021c89 | 2014-03-17 12:57:50 -0700 | [diff] [blame] | 661 | private final String mSignature; |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 662 | |
| 663 | public VideoData(long id, String title, String mimeType, |
| 664 | long dateTakenInSeconds, long dateModifiedInSeconds, |
| 665 | String path, int width, int height, long sizeInBytes, |
| 666 | double latitude, double longitude, long durationInSeconds) { |
| 667 | super(id, title, mimeType, dateTakenInSeconds, dateModifiedInSeconds, |
| 668 | path, width, height, sizeInBytes, latitude, longitude); |
| 669 | mDurationInSeconds = durationInSeconds; |
Sam Judd | 4021c89 | 2014-03-17 12:57:50 -0700 | [diff] [blame] | 670 | mSignature = mimeType + dateModifiedInSeconds; |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 671 | } |
Sascha Haeberling | b0a5929 | 2013-08-19 14:02:19 -0700 | [diff] [blame] | 672 | |
Angus Kong | 571a8c3 | 2014-03-13 12:53:03 -0700 | [diff] [blame] | 673 | public static LocalData fromContentUri(ContentResolver cr, Uri contentUri) { |
| 674 | List<LocalData> newVideos = query(cr, contentUri, QUERY_ALL_MEDIA_ID); |
| 675 | if (newVideos.isEmpty()) { |
| 676 | return null; |
| 677 | } |
| 678 | return newVideos.get(0); |
| 679 | } |
| 680 | |
| 681 | static List<LocalData> query(ContentResolver cr, Uri uri, long lastId) { |
| 682 | return queryLocalMediaData(cr, uri, QUERY_PROJECTION, lastId, QUERY_ORDER, |
| 683 | new VideoDataBuilder()); |
Sam Judd | aeed91f | 2014-03-13 11:40:10 -0700 | [diff] [blame] | 684 | } |
| 685 | |
Sam Judd | de3e9ab | 2014-03-17 13:07:22 -0700 | [diff] [blame] | 686 | /** |
| 687 | * We can't trust the media store and we can't afford the performance overhead of |
| 688 | * synchronously decoding the video header for every item when loading our data set |
| 689 | * from the media store, so we instead run the metadata loader in the background |
| 690 | * to decode the video header for each item and prefer whatever values it obtains. |
| 691 | */ |
| 692 | private int getBestWidth() { |
| 693 | int metadataWidth = VideoRotationMetadataLoader.getWidth(this); |
| 694 | if (metadataWidth > 0) { |
| 695 | return metadataWidth; |
| 696 | } else { |
| 697 | return mWidth; |
| 698 | } |
| 699 | } |
| 700 | |
| 701 | private int getBestHeight() { |
| 702 | int metadataHeight = VideoRotationMetadataLoader.getHeight(this); |
| 703 | if (metadataHeight > 0) { |
| 704 | return metadataHeight; |
| 705 | } else { |
| 706 | return mHeight; |
| 707 | } |
| 708 | } |
| 709 | |
| 710 | /** |
| 711 | * If the metadata loader has determined from the video header that we need to rotate the video |
| 712 | * 90 or 270 degrees, then we swap the width and height. |
| 713 | */ |
| 714 | @Override |
| 715 | public int getWidth() { |
| 716 | return VideoRotationMetadataLoader.isRotated(this) ? getBestHeight() : getBestWidth(); |
| 717 | } |
| 718 | |
| 719 | @Override |
| 720 | public int getHeight() { |
| 721 | return VideoRotationMetadataLoader.isRotated(this) ? getBestWidth() : getBestHeight(); |
| 722 | } |
| 723 | |
Sam Judd | aeed91f | 2014-03-13 11:40:10 -0700 | [diff] [blame] | 724 | private static VideoData buildFromCursor(Cursor c) { |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 725 | long id = c.getLong(COL_ID); |
| 726 | String title = c.getString(COL_TITLE); |
| 727 | String mimeType = c.getString(COL_MIME_TYPE); |
| 728 | long dateTakenInSeconds = c.getLong(COL_DATE_TAKEN); |
| 729 | long dateModifiedInSeconds = c.getLong(COL_DATE_MODIFIED); |
| 730 | String path = c.getString(COL_DATA); |
| 731 | int width = c.getInt(COL_WIDTH); |
| 732 | int height = c.getInt(COL_HEIGHT); |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 733 | |
Sam Judd | de3e9ab | 2014-03-17 13:07:22 -0700 | [diff] [blame] | 734 | // If the media store doesn't contain a width and a height, use the width and height |
| 735 | // of the default camera mode instead. When the metadata loader runs, it will set the |
| 736 | // correct values. |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 737 | if (width == 0 || height == 0) { |
Sam Judd | de3e9ab | 2014-03-17 13:07:22 -0700 | [diff] [blame] | 738 | Log.w(TAG, "failed to retrieve width and height from the media store, defaulting " + |
| 739 | " to camera profile"); |
| 740 | CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH); |
| 741 | width = profile.videoFrameWidth; |
| 742 | height = profile.videoFrameHeight; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 743 | } |
Sascha Haeberling | b0a5929 | 2013-08-19 14:02:19 -0700 | [diff] [blame] | 744 | |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 745 | long sizeInBytes = c.getLong(COL_SIZE); |
| 746 | double latitude = c.getDouble(COL_LATITUDE); |
| 747 | double longitude = c.getDouble(COL_LONGITUDE); |
| 748 | long durationInSeconds = c.getLong(COL_DURATION) / 1000; |
| 749 | VideoData d = new VideoData(id, title, mimeType, dateTakenInSeconds, |
| 750 | dateModifiedInSeconds, path, width, height, sizeInBytes, |
| 751 | latitude, longitude, durationInSeconds); |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 752 | return d; |
| 753 | } |
| 754 | |
| 755 | @Override |
| 756 | public String toString() { |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 757 | return "Video:" + ",data=" + mPath + ",mimeType=" + mMimeType |
| 758 | + "," + mWidth + "x" + mHeight + ",date=" + new Date(mDateTakenInSeconds); |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 759 | } |
| 760 | |
| 761 | @Override |
Angus Kong | c27d21b | 2013-08-12 15:03:45 -0700 | [diff] [blame] | 762 | public int getViewType() { |
Angus Kong | b21215a | 2013-09-20 18:41:46 -0700 | [diff] [blame] | 763 | return VIEW_TYPE_REMOVABLE; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 764 | } |
| 765 | |
| 766 | @Override |
| 767 | public boolean isUIActionSupported(int action) { |
| 768 | return ((action & mSupportedUIActions) == action); |
| 769 | } |
| 770 | |
| 771 | @Override |
| 772 | public boolean isDataActionSupported(int action) { |
| 773 | return ((action & mSupportedDataActions) == action); |
| 774 | } |
| 775 | |
| 776 | @Override |
Angus Kong | 8a2350a | 2013-12-16 15:02:34 -0800 | [diff] [blame] | 777 | public boolean delete(Context context) { |
| 778 | ContentResolver cr = context.getContentResolver(); |
Angus Kong | 3250987 | 2013-10-02 16:53:40 -0700 | [diff] [blame] | 779 | cr.delete(CONTENT_URI, MediaStore.Video.VideoColumns._ID + "=" + mContentId, null); |
Angus Kong | 8a2350a | 2013-12-16 15:02:34 -0800 | [diff] [blame] | 780 | return super.delete(context); |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 781 | } |
| 782 | |
| 783 | @Override |
Angus Kong | 571a8c3 | 2014-03-13 12:53:03 -0700 | [diff] [blame] | 784 | public Uri getUri() { |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 785 | Uri baseUri = CONTENT_URI; |
Angus Kong | 3250987 | 2013-10-02 16:53:40 -0700 | [diff] [blame] | 786 | return baseUri.buildUpon().appendPath(String.valueOf(mContentId)).build(); |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 787 | } |
| 788 | |
| 789 | @Override |
Sascha Haeberling | 6f64b50 | 2013-08-14 16:23:18 -0700 | [diff] [blame] | 790 | public MediaDetails getMediaDetails(Context context) { |
Sascha Haeberling | b0a5929 | 2013-08-19 14:02:19 -0700 | [diff] [blame] | 791 | MediaDetails mediaDetails = super.getMediaDetails(context); |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 792 | String duration = MediaDetails.formatDuration(context, mDurationInSeconds); |
Sascha Haeberling | b0a5929 | 2013-08-19 14:02:19 -0700 | [diff] [blame] | 793 | mediaDetails.addDetail(MediaDetails.INDEX_DURATION, duration); |
| 794 | return mediaDetails; |
Sascha Haeberling | 6f64b50 | 2013-08-14 16:23:18 -0700 | [diff] [blame] | 795 | } |
| 796 | |
| 797 | @Override |
Sascha Haeberling | fae11a1 | 2013-08-15 14:29:49 -0700 | [diff] [blame] | 798 | public int getLocalDataType() { |
Angus Kong | c27d21b | 2013-08-12 15:03:45 -0700 | [diff] [blame] | 799 | return LOCAL_VIDEO; |
| 800 | } |
| 801 | |
| 802 | @Override |
Angus Kong | 8a2350a | 2013-12-16 15:02:34 -0800 | [diff] [blame] | 803 | public LocalData refresh(Context context) { |
Angus Kong | 571a8c3 | 2014-03-13 12:53:03 -0700 | [diff] [blame] | 804 | Cursor c = context.getContentResolver().query(getUri(), QUERY_PROJECTION, null, |
Angus Kong | 8a2350a | 2013-12-16 15:02:34 -0800 | [diff] [blame] | 805 | null, null); |
Sascha Haeberling | b0a5929 | 2013-08-19 14:02:19 -0700 | [diff] [blame] | 806 | if (c == null || !c.moveToFirst()) { |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 807 | return null; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 808 | } |
| 809 | VideoData newData = buildFromCursor(c); |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 810 | return newData; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 811 | } |
| 812 | |
| 813 | @Override |
Sam Judd | 4021c89 | 2014-03-17 12:57:50 -0700 | [diff] [blame] | 814 | public String getSignature() { |
| 815 | return mSignature; |
| 816 | } |
| 817 | |
| 818 | @Override |
| 819 | protected ImageView fillImageView(Context context, final ImageView v, final int decodeWidth, |
| 820 | final int decodeHeight, int placeHolderResourceId, LocalDataAdapter adapter, |
| 821 | boolean isInProgress) { |
| 822 | SizedImageViewTarget target = (SizedImageViewTarget) v.getTag(); |
| 823 | if (target == null) { |
| 824 | target = new SizedImageViewTarget(v); |
| 825 | v.setTag(target); |
| 826 | } |
| 827 | target.setSize(decodeWidth, decodeHeight); |
| 828 | |
| 829 | Glide.with(context) |
| 830 | .using(new VideoModelLoader(context)) |
| 831 | .loadFromVideo(this) |
| 832 | .placeholder(placeHolderResourceId) |
| 833 | .fitCenter() |
| 834 | .into(target); |
| 835 | |
| 836 | return v; |
| 837 | } |
| 838 | |
| 839 | @Override |
Sam Judd | 43bf03f | 2014-03-17 11:27:03 -0700 | [diff] [blame] | 840 | public View getView(final Context context, View recycled, |
Sam Judd | 4021c89 | 2014-03-17 12:57:50 -0700 | [diff] [blame] | 841 | int decodeWidth, int decodeHeight, int placeHolderResourceId, |
Sascha Haeberling | 14ff6c8 | 2013-12-13 13:29:58 -0800 | [diff] [blame] | 842 | LocalDataAdapter adapter, boolean isInProgress) { |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 843 | |
Sam Judd | 43bf03f | 2014-03-17 11:27:03 -0700 | [diff] [blame] | 844 | final VideoViewHolder viewHolder; |
| 845 | final View result; |
| 846 | if (recycled != null) { |
| 847 | result = recycled; |
| 848 | viewHolder = (VideoViewHolder) recycled.getTag(); |
| 849 | } else { |
| 850 | result = LayoutInflater.from(context).inflate(R.layout.filmstrip_video, null); |
| 851 | ImageView videoView = (ImageView) result.findViewById(R.id.video_view); |
| 852 | ImageView playButton = (ImageView) result.findViewById(R.id.play_button); |
| 853 | viewHolder = new VideoViewHolder(videoView, playButton); |
| 854 | result.setTag(viewHolder); |
| 855 | } |
| 856 | |
Sam Judd | 4021c89 | 2014-03-17 12:57:50 -0700 | [diff] [blame] | 857 | fillImageView(context, viewHolder.mVideoView, decodeWidth, decodeHeight, |
| 858 | placeHolderResourceId, adapter, isInProgress); |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 859 | |
| 860 | // ImageView for the play icon. |
Sam Judd | 43bf03f | 2014-03-17 11:27:03 -0700 | [diff] [blame] | 861 | viewHolder.mPlayButton.setOnClickListener(new View.OnClickListener() { |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 862 | @Override |
| 863 | public void onClick(View v) { |
Sascha Haeberling | 280fd3e | 2013-11-21 13:52:15 -0800 | [diff] [blame] | 864 | // TODO: refactor this into activities to avoid this class |
| 865 | // conversion. |
Angus Kong | 571a8c3 | 2014-03-13 12:53:03 -0700 | [diff] [blame] | 866 | CameraUtil.playVideo((Activity) context, getUri(), mTitle); |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 867 | } |
| 868 | }); |
| 869 | |
Sam Judd | 43bf03f | 2014-03-17 11:27:03 -0700 | [diff] [blame] | 870 | return result; |
| 871 | } |
| 872 | |
| 873 | @Override |
Sam Judd | 4021c89 | 2014-03-17 12:57:50 -0700 | [diff] [blame] | 874 | public void recycle(View view) { |
| 875 | super.recycle(view); |
| 876 | VideoViewHolder videoViewHolder = (VideoViewHolder) view.getTag(); |
| 877 | Target target = (Target) videoViewHolder.mVideoView.getTag(); |
| 878 | Glide.cancel(target); |
| 879 | } |
| 880 | |
| 881 | @Override |
Sam Judd | 43bf03f | 2014-03-17 11:27:03 -0700 | [diff] [blame] | 882 | public LocalDataViewType getItemViewType() { |
| 883 | return LocalDataViewType.VIDEO; |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 884 | } |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 885 | } |
Andy Huibers | caca8c7 | 2013-12-13 15:53:43 -0800 | [diff] [blame] | 886 | |
Sam Judd | aeed91f | 2014-03-13 11:40:10 -0700 | [diff] [blame] | 887 | private static class VideoDataBuilder implements CursorToLocalData { |
| 888 | |
| 889 | @Override |
| 890 | public VideoData build(Cursor cursor) { |
| 891 | return LocalMediaData.VideoData.buildFromCursor(cursor); |
| 892 | } |
| 893 | } |
| 894 | |
Sam Judd | 43bf03f | 2014-03-17 11:27:03 -0700 | [diff] [blame] | 895 | private static class VideoViewHolder { |
| 896 | private final ImageView mVideoView; |
| 897 | private final ImageView mPlayButton; |
| 898 | |
| 899 | public VideoViewHolder(ImageView videoView, ImageView playButton) { |
| 900 | mVideoView = videoView; |
| 901 | mPlayButton = playButton; |
| 902 | } |
| 903 | } |
Sam Judd | 4021c89 | 2014-03-17 12:57:50 -0700 | [diff] [blame] | 904 | |
| 905 | /** |
| 906 | * Normally Glide will figure out the necessary size based on the view |
| 907 | * the image is being loaded into. In filmstrip the view isn't immediately |
| 908 | * laid out after being requested from the data, which can cause Glide to give |
| 909 | * up on obtaining the view dimensions. To avoid that, we manually set the |
| 910 | * dimensions. |
| 911 | */ |
| 912 | private static class SizedImageViewTarget extends ImageViewTarget { |
| 913 | private int mWidth; |
| 914 | private int mHeight; |
| 915 | |
| 916 | public SizedImageViewTarget(ImageView imageView) { |
| 917 | super(imageView); |
| 918 | } |
| 919 | |
| 920 | public void setSize(int width, int height) { |
| 921 | mWidth = width; |
| 922 | mHeight = height; |
| 923 | } |
| 924 | |
| 925 | @Override |
| 926 | public void getSize(final SizeReadyCallback cb) { |
| 927 | cb.onSizeReady(mWidth, mHeight); |
| 928 | } |
| 929 | } |
Angus Kong | bd26069 | 2013-08-07 14:52:56 -0700 | [diff] [blame] | 930 | } |