blob: 766ed3aacefa7f10d4bc8603e8c5c8295b6c1783 [file] [log] [blame]
Angus Kong49b9ba22013-05-13 13:42:21 -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.ContentResolver;
20import android.content.Context;
Angus Kong885367c2013-05-31 16:30:32 -070021import android.net.Uri;
Angus Kong49b9ba22013-05-13 13:42:21 -070022import android.os.AsyncTask;
Angus Kong49b9ba22013-05-13 13:42:21 -070023import android.view.View;
Angus Kong49b9ba22013-05-13 13:42:21 -070024
Angus Kong2bca2102014-03-11 16:27:30 -070025import com.android.camera.debug.Log;
Angus Kong01054e92013-12-10 11:06:18 -080026import com.android.camera.filmstrip.ImageData;
Angus Kong49b9ba22013-05-13 13:42:21 -070027
Sascha Haeberling37f36112013-08-06 14:31:52 -070028import java.util.ArrayList;
Sascha Haeberling37f36112013-08-06 14:31:52 -070029import java.util.Comparator;
Angus Konge2f4c032013-12-19 10:24:33 -080030import java.util.List;
Sascha Haeberling37f36112013-08-06 14:31:52 -070031
Angus Kong49b9ba22013-05-13 13:42:21 -070032/**
Angus Kong8e5e4ee2013-07-30 11:36:00 -070033 * A {@link LocalDataAdapter} that provides data in the camera folder.
Angus Kong49b9ba22013-05-13 13:42:21 -070034 */
Angus Kong8e5e4ee2013-07-30 11:36:00 -070035public class CameraDataAdapter implements LocalDataAdapter {
Angus Kong2bca2102014-03-11 16:27:30 -070036 private static final Log.Tag TAG = new Log.Tag("CameraDataAdapter");
Angus Kong49b9ba22013-05-13 13:42:21 -070037
Angus Kong653c43b2013-08-21 18:28:43 -070038 private static final int DEFAULT_DECODE_SIZE = 1600;
Angus Kong49b9ba22013-05-13 13:42:21 -070039
Sascha Haeberling44c1afb2013-12-20 11:59:35 -080040 private final Context mContext;
41
Sascha Haeberlingc2915692013-09-16 11:04:25 -070042 private LocalDataList mImages;
Angus Kong49b9ba22013-05-13 13:42:21 -070043
44 private Listener mListener;
Angus Konge2f4c032013-12-19 10:24:33 -080045 private LocalDataListener mLocalDataListener;
Sam Judd4021c892014-03-17 12:57:50 -070046 private final int mPlaceHolderResourceId;
Angus Kong49b9ba22013-05-13 13:42:21 -070047
48 private int mSuggestedWidth = DEFAULT_DECODE_SIZE;
49 private int mSuggestedHeight = DEFAULT_DECODE_SIZE;
Sam Juddaeed91f2014-03-13 11:40:10 -070050 private long mLastPhotoId = LocalMediaData.QUERY_ALL_MEDIA_ID;
Angus Kong49b9ba22013-05-13 13:42:21 -070051
Angus Kong08221032013-06-14 14:50:31 -070052 private LocalData mLocalDataToDelete;
Angus Kong49448f82013-06-05 13:12:39 -070053
Sam Judd4021c892014-03-17 12:57:50 -070054 public CameraDataAdapter(Context context, int placeholderResource) {
Sascha Haeberling44c1afb2013-12-20 11:59:35 -080055 mContext = context;
Sascha Haeberlingc2915692013-09-16 11:04:25 -070056 mImages = new LocalDataList();
Sam Judd4021c892014-03-17 12:57:50 -070057 mPlaceHolderResourceId = placeholderResource;
Angus Kong49b9ba22013-05-13 13:42:21 -070058 }
59
Angus Kong8e5e4ee2013-07-30 11:36:00 -070060 @Override
Angus Konge2f4c032013-12-19 10:24:33 -080061 public void setLocalDataListener(LocalDataListener listener) {
62 mLocalDataListener = listener;
63 }
64
65 @Override
Sam Juddaeed91f2014-03-13 11:40:10 -070066 public void requestLoadNewPhotos() {
67 LoadNewPhotosTask ltask = new LoadNewPhotosTask(mLastPhotoId);
68 ltask.execute(mContext.getContentResolver());
69 }
70
71 @Override
Sascha Haeberling44c1afb2013-12-20 11:59:35 -080072 public void requestLoad() {
73 QueryTask qtask = new QueryTask();
Sam Juddde3e9ab2014-03-17 13:07:22 -070074 qtask.execute(mContext);
Angus Kong49b9ba22013-05-13 13:42:21 -070075 }
76
Angus Konge2f4c032013-12-19 10:24:33 -080077 @Override
Sam Juddde3e9ab2014-03-17 13:07:22 -070078 public AsyncTask updateMetadata(int dataId) {
79 MetadataUpdateTask result = new MetadataUpdateTask();
80 result.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, dataId);
81 return result;
Angus Konge2f4c032013-12-19 10:24:33 -080082 }
83
84 @Override
85 public boolean isMetadataUpdated(int dataId) {
86 if (dataId < 0 || dataId >= mImages.size()) {
87 return true;
88 }
89 return mImages.get(dataId).isMetadataUpdated();
90 }
91
Angus Kong49b9ba22013-05-13 13:42:21 -070092 @Override
Sam Judd43bf03f2014-03-17 11:27:03 -070093 public int getItemViewType(int dataId) {
94 if (dataId > mImages.size() || dataId < 0) {
95 return -1;
96 }
97
98 return mImages.get(dataId).getItemViewType().ordinal();
99 }
100
101 @Override
Sascha Haeberling37f36112013-08-06 14:31:52 -0700102 public LocalData getLocalData(int dataID) {
Angus Kong81c59922013-08-09 17:09:35 -0700103 if (dataID < 0 || dataID >= mImages.size()) {
Sascha Haeberling37f36112013-08-06 14:31:52 -0700104 return null;
105 }
106
107 return mImages.get(dataID);
108 }
109
110 @Override
Angus Kong49b9ba22013-05-13 13:42:21 -0700111 public int getTotalNumber() {
Angus Kong49b9ba22013-05-13 13:42:21 -0700112 return mImages.size();
113 }
114
115 @Override
Angus Kong01054e92013-12-10 11:06:18 -0800116 public ImageData getImageData(int id) {
Sascha Haeberling37f36112013-08-06 14:31:52 -0700117 return getLocalData(id);
Angus Kong08221032013-06-14 14:50:31 -0700118 }
119
Angus Kong49b9ba22013-05-13 13:42:21 -0700120 @Override
Angus Kong8e5e4ee2013-07-30 11:36:00 -0700121 public void suggestViewSizeBound(int w, int h) {
Andy Huiberscaca8c72013-12-13 15:53:43 -0800122 mSuggestedWidth = w;
123 mSuggestedHeight = h;
Angus Kong49b9ba22013-05-13 13:42:21 -0700124 }
125
126 @Override
Sam Judd43bf03f2014-03-17 11:27:03 -0700127 public View getView(Context context, View recycled, int dataID) {
Angus Kong49b9ba22013-05-13 13:42:21 -0700128 if (dataID >= mImages.size() || dataID < 0) {
129 return null;
130 }
131
132 return mImages.get(dataID).getView(
Sam Judd43bf03f2014-03-17 11:27:03 -0700133 context, recycled, mSuggestedWidth, mSuggestedHeight,
Sam Judd4021c892014-03-17 12:57:50 -0700134 mPlaceHolderResourceId, this, /* inProgress */ false);
Angus Kong49b9ba22013-05-13 13:42:21 -0700135 }
136
137 @Override
Andy Huiberscaca8c72013-12-13 15:53:43 -0800138 public void resizeView(Context context, int dataID, View view, int w, int h) {
139 if (dataID >= mImages.size() || dataID < 0) {
140 return;
141 }
Sam Judd4021c892014-03-17 12:57:50 -0700142 mImages.get(dataID).loadFullImage(context, w, h, view, this);
Andy Huiberscaca8c72013-12-13 15:53:43 -0800143 }
144
145 @Override
Angus Kong49b9ba22013-05-13 13:42:21 -0700146 public void setListener(Listener listener) {
147 mListener = listener;
Angus Kongcb8942b2013-12-16 14:32:49 -0800148 if (mImages.size() != 0) {
Angus Kong750e8ec2013-05-06 10:42:28 -0700149 mListener.onDataLoaded();
150 }
Angus Kong49b9ba22013-05-13 13:42:21 -0700151 }
152
Angus Kong49448f82013-06-05 13:12:39 -0700153 @Override
Angus Kong8e5e4ee2013-07-30 11:36:00 -0700154 public boolean canSwipeInFullScreen(int dataID) {
155 if (dataID < mImages.size() && dataID > 0) {
156 return mImages.get(dataID).canSwipeInFullScreen();
157 }
158 return true;
159 }
160
161 @Override
Sascha Haeberling44c1afb2013-12-20 11:59:35 -0800162 public void removeData(int dataID) {
Sascha Haeberling14ff6c82013-12-13 13:29:58 -0800163 if (dataID >= mImages.size()) {
164 return;
165 }
Angus Kong87f9a622013-05-16 16:59:39 -0700166 LocalData d = mImages.remove(dataID);
Angus Kong08221032013-06-14 14:50:31 -0700167 // Delete previously removed data first.
Sascha Haeberling44c1afb2013-12-20 11:59:35 -0800168 executeDeletion();
Angus Kong08221032013-06-14 14:50:31 -0700169 mLocalDataToDelete = d;
Angus Kong87f9a622013-05-16 16:59:39 -0700170 mListener.onDataRemoved(dataID, d);
171 }
172
Angus Kong8e5e4ee2013-07-30 11:36:00 -0700173 @Override
Angus Kong571a8c32014-03-13 12:53:03 -0700174 public boolean addData(LocalData newData) {
175 final Uri uri = newData.getUri();
Sascha Haeberling37f36112013-08-06 14:31:52 -0700176 int pos = findDataByContentUri(uri);
Sascha Haeberling37f36112013-08-06 14:31:52 -0700177 if (pos != -1) {
178 // a duplicate one, just do a substitute.
Sam Juddaeed91f2014-03-13 11:40:10 -0700179 Log.v(TAG, "found duplicate data");
Sascha Haeberling37f36112013-08-06 14:31:52 -0700180 updateData(pos, newData);
Angus Kong571a8c32014-03-13 12:53:03 -0700181 return false;
Sascha Haeberling37f36112013-08-06 14:31:52 -0700182 } else {
183 // a new data.
184 insertData(newData);
Angus Kong571a8c32014-03-13 12:53:03 -0700185 return true;
Angus Kong885367c2013-05-31 16:30:32 -0700186 }
187 }
188
Angus Kong8e5e4ee2013-07-30 11:36:00 -0700189 @Override
190 public int findDataByContentUri(Uri uri) {
Sascha Haeberlingc2915692013-09-16 11:04:25 -0700191 // LocalDataList will return in O(1) if the uri is not contained.
192 // Otherwise the performance is O(n), but this is acceptable as we will
193 // most often call this to find an element at the beginning of the list.
194 return mImages.indexOf(uri);
Angus Kong8e5e4ee2013-07-30 11:36:00 -0700195 }
196
197 @Override
Angus Kong08221032013-06-14 14:50:31 -0700198 public boolean undoDataRemoval() {
199 if (mLocalDataToDelete == null) return false;
200 LocalData d = mLocalDataToDelete;
201 mLocalDataToDelete = null;
202 insertData(d);
203 return true;
204 }
205
Angus Kong8e5e4ee2013-07-30 11:36:00 -0700206 @Override
Sascha Haeberling44c1afb2013-12-20 11:59:35 -0800207 public boolean executeDeletion() {
Angus Kong08221032013-06-14 14:50:31 -0700208 if (mLocalDataToDelete == null) return false;
209
Sascha Haeberling44c1afb2013-12-20 11:59:35 -0800210 DeletionTask task = new DeletionTask();
Angus Kong08221032013-06-14 14:50:31 -0700211 task.execute(mLocalDataToDelete);
212 mLocalDataToDelete = null;
213 return true;
214 }
215
Angus Kong8e5e4ee2013-07-30 11:36:00 -0700216 @Override
217 public void flush() {
Sascha Haeberlingc2915692013-09-16 11:04:25 -0700218 replaceData(new LocalDataList());
Angus Kong8e5e4ee2013-07-30 11:36:00 -0700219 }
220
Sascha Haeberling37f36112013-08-06 14:31:52 -0700221 @Override
Seth Raphael455ba5a2014-02-13 15:10:06 -0800222 public void refresh(Uri uri) {
223 final int pos = findDataByContentUri(uri);
Sascha Haeberling37f36112013-08-06 14:31:52 -0700224 if (pos == -1) {
225 return;
Angus Kong8e5e4ee2013-07-30 11:36:00 -0700226 }
Sascha Haeberling37f36112013-08-06 14:31:52 -0700227
228 LocalData data = mImages.get(pos);
Sascha Haeberling44c1afb2013-12-20 11:59:35 -0800229 LocalData refreshedData = data.refresh(mContext);
Sascha Haeberling14ff6c82013-12-13 13:29:58 -0800230
Angus Kong2520b502014-01-10 17:45:27 -0800231 // Refresh failed. Probably removed already.
232 if (refreshedData == null && mListener != null) {
233 mListener.onDataRemoved(pos, data);
234 return;
235 }
Angus Kong2520b502014-01-10 17:45:27 -0800236 updateData(pos, refreshedData);
Angus Kong8e5e4ee2013-07-30 11:36:00 -0700237 }
238
Sascha Haeberling37f36112013-08-06 14:31:52 -0700239 @Override
240 public void updateData(final int pos, LocalData data) {
241 mImages.set(pos, data);
242 if (mListener != null) {
243 mListener.onDataUpdated(new UpdateReporter() {
244 @Override
245 public boolean isDataRemoved(int dataID) {
246 return false;
247 }
248
249 @Override
250 public boolean isDataUpdated(int dataID) {
251 return (dataID == pos);
252 }
253 });
254 }
255 }
256
Angus Kong571a8c32014-03-13 12:53:03 -0700257 private void insertData(LocalData data) {
Sascha Haeberling37f36112013-08-06 14:31:52 -0700258 // Since this function is mostly for adding the newest data,
259 // a simple linear search should yield the best performance over a
260 // binary search.
261 int pos = 0;
262 Comparator<LocalData> comp = new LocalData.NewestFirstComparator();
263 for (; pos < mImages.size()
264 && comp.compare(data, mImages.get(pos)) > 0; pos++);
265 mImages.add(pos, data);
Sam Juddde3e9ab2014-03-17 13:07:22 -0700266 updateMetadata(pos);
Sascha Haeberling37f36112013-08-06 14:31:52 -0700267 if (mListener != null) {
268 mListener.onDataInserted(pos, data);
269 }
270 }
Angus Konga06b4e22013-06-03 11:10:11 -0700271
Sascha Haeberling37f36112013-08-06 14:31:52 -0700272 /** Update all the data */
Sascha Haeberlingc2915692013-09-16 11:04:25 -0700273 private void replaceData(LocalDataList list) {
Angus Kongd806e652013-08-12 15:54:46 -0700274 if (list.size() == 0 && mImages.size() == 0) {
275 return;
276 }
Sascha Haeberling37f36112013-08-06 14:31:52 -0700277 mImages = list;
Angus Kongd806e652013-08-12 15:54:46 -0700278 if (mListener != null) {
Sascha Haeberling37f36112013-08-06 14:31:52 -0700279 mListener.onDataLoaded();
Angus Konga06b4e22013-06-03 11:10:11 -0700280 }
281 }
282
Sam Juddde3e9ab2014-03-17 13:07:22 -0700283 @Override
284 public List<AsyncTask> preloadItems(List<Integer> items) {
285 List<AsyncTask> result = new ArrayList<AsyncTask>();
286 for (Integer id : items) {
287 if (!isMetadataUpdated(id)) {
288 result.add(updateMetadata(id));
289 }
290 }
291 return result;
292 }
293
294 @Override
295 public void cancelItems(List<AsyncTask> loadTokens) {
296 for (AsyncTask asyncTask : loadTokens) {
297 if (asyncTask != null) {
298 asyncTask.cancel(false);
299 }
300 }
301 }
302
303 @Override
304 public List<Integer> getItemsInRange(int startPosition, int endPosition) {
305 List<Integer> result = new ArrayList<Integer>();
306 for (int i = Math.max(0, startPosition); i < endPosition; i++) {
307 result.add(i);
308 }
309 return result;
310 }
311
312 @Override
313 public int getCount() {
314 return getTotalNumber();
315 }
316
Sam Juddaeed91f2014-03-13 11:40:10 -0700317 private class LoadNewPhotosTask extends AsyncTask<ContentResolver, Void, List<LocalData>> {
318
319 private long mMinPhotoId;
320
321 public LoadNewPhotosTask(long lastPhotoId) {
322 mMinPhotoId = lastPhotoId;
323 }
324
325 /**
326 * Loads any new photos added to our storage directory since our last query.
327 * @param contentResolvers {@link android.content.ContentResolver} to load data.
328 * @return An {@link java.util.ArrayList} containing any new data.
329 */
330 @Override
331 protected List<LocalData> doInBackground(ContentResolver... contentResolvers) {
332 final ContentResolver cr = contentResolvers[0];
333 return LocalMediaData.PhotoData.query(cr, LocalMediaData.PhotoData.CONTENT_URI,
334 mMinPhotoId);
335 }
336
337 @Override
338 protected void onPostExecute(List<LocalData> newPhotoData) {
339 if (!newPhotoData.isEmpty()) {
340 LocalData newestPhoto = newPhotoData.get(0);
341 // We may overlap with another load task or a query task, in which case we want
342 // to be sure we never decrement the oldest seen id.
343 mLastPhotoId = Math.max(mLastPhotoId, newestPhoto.getContentId());
344 }
345 // We may add data that is already present, but if we do, it will be deduped in addData.
346 for (LocalData localData : newPhotoData) {
Angus Kong571a8c32014-03-13 12:53:03 -0700347 addData(localData);
Sam Juddaeed91f2014-03-13 11:40:10 -0700348 }
349 }
350 }
351
352 private class QueryTaskResult {
353 public LocalDataList mLocalDataList;
354 public long mLastPhotoId;
355
356 public QueryTaskResult(LocalDataList localDataList, long lastPhotoId) {
357 mLocalDataList = localDataList;
358 mLastPhotoId = lastPhotoId;
359 }
360 }
361
Sam Juddde3e9ab2014-03-17 13:07:22 -0700362 private class QueryTask extends AsyncTask<Context, Void, QueryTaskResult> {
363 // The maximum number of data to load metadata for in a single task.
364 private static final int MAX_METADATA = 5;
Sam Juddaeed91f2014-03-13 11:40:10 -0700365
Angus Kongdcccc512013-08-08 17:06:03 -0700366 /**
367 * Loads all the photo and video data in the camera folder in background
368 * and combine them into one single list.
369 *
Sam Juddde3e9ab2014-03-17 13:07:22 -0700370 * @param contexts {@link Context} to load all the data.
Sam Juddaeed91f2014-03-13 11:40:10 -0700371 * @return An {@link com.android.camera.data.CameraDataAdapter.QueryTaskResult} containing
372 * all loaded data and the highest photo id in the dataset.
Angus Kongdcccc512013-08-08 17:06:03 -0700373 */
Angus Kong49b9ba22013-05-13 13:42:21 -0700374 @Override
Sam Juddde3e9ab2014-03-17 13:07:22 -0700375 protected QueryTaskResult doInBackground(Context... contexts) {
376 final Context context = contexts[0];
377 final ContentResolver cr = context.getContentResolver();
Sascha Haeberlingc2915692013-09-16 11:04:25 -0700378 LocalDataList l = new LocalDataList();
Angus Kong49b9ba22013-05-13 13:42:21 -0700379 // Photos
Sam Juddaeed91f2014-03-13 11:40:10 -0700380 List<LocalData> photoData = LocalMediaData.PhotoData.query(cr,
381 LocalMediaData.PhotoData.CONTENT_URI, LocalMediaData.QUERY_ALL_MEDIA_ID);
382 List<LocalData> videoData = LocalMediaData.VideoData.query(cr,
Angus Kong571a8c32014-03-13 12:53:03 -0700383 LocalMediaData.VideoData.CONTENT_URI, LocalMediaData.QUERY_ALL_MEDIA_ID);
Sam Juddaeed91f2014-03-13 11:40:10 -0700384
385 long lastPhotoId = LocalMediaData.QUERY_ALL_MEDIA_ID;
386 if (!photoData.isEmpty()) {
387 lastPhotoId = photoData.get(0).getContentId();
Angus Kong750e8ec2013-05-06 10:42:28 -0700388 }
Angus Kong49b9ba22013-05-13 13:42:21 -0700389
Sam Juddaeed91f2014-03-13 11:40:10 -0700390 l.addAll(photoData);
391 l.addAll(videoData);
392 l.sort(new LocalData.NewestFirstComparator());
Angus Kong49b9ba22013-05-13 13:42:21 -0700393
Sam Juddde3e9ab2014-03-17 13:07:22 -0700394 // Load enough metadata so it's already loaded when we open the filmstrip.
395 for (int i = 0; i < MAX_METADATA && i < l.size(); i++) {
396 LocalData data = l.get(i);
397 MetadataLoader.loadMetadata(context, data);
398 }
399
Sam Juddaeed91f2014-03-13 11:40:10 -0700400 return new QueryTaskResult(l, lastPhotoId);
Angus Kong49b9ba22013-05-13 13:42:21 -0700401 }
402
403 @Override
Sam Juddaeed91f2014-03-13 11:40:10 -0700404 protected void onPostExecute(QueryTaskResult result) {
405 // Since we're wiping away all of our data, we should always replace any existing last
406 // photo id with the new one we just obtained so it matches the data we're showing.
407 mLastPhotoId = result.mLastPhotoId;
408 replaceData(result.mLocalDataList);
Angus Kong49b9ba22013-05-13 13:42:21 -0700409 }
410 }
411
Angus Kong08221032013-06-14 14:50:31 -0700412 private class DeletionTask extends AsyncTask<LocalData, Void, Void> {
Angus Kong08221032013-06-14 14:50:31 -0700413 @Override
414 protected Void doInBackground(LocalData... data) {
415 for (int i = 0; i < data.length; i++) {
Angus Kong740cbee2013-12-11 15:46:16 -0800416 if (!data[i].isDataActionSupported(LocalData.DATA_ACTION_DELETE)) {
Angus Kong08221032013-06-14 14:50:31 -0700417 Log.v(TAG, "Deletion is not supported:" + data[i]);
418 continue;
419 }
420 data[i].delete(mContext);
421 }
422 return null;
423 }
424 }
Angus Konge2f4c032013-12-19 10:24:33 -0800425
426 private class MetadataUpdateTask extends AsyncTask<Integer, Void, List<Integer> > {
Angus Konge2f4c032013-12-19 10:24:33 -0800427 @Override
428 protected List<Integer> doInBackground(Integer... dataId) {
429 List<Integer> updatedList = new ArrayList<Integer>();
430 for (Integer id : dataId) {
431 if (id < 0 || id >= mImages.size()) {
432 continue;
433 }
434 final LocalData data = mImages.get(id);
Sam Juddde3e9ab2014-03-17 13:07:22 -0700435 if (MetadataLoader.loadMetadata(mContext, data)) {
436 updatedList.add(id);
Angus Konge2f4c032013-12-19 10:24:33 -0800437 }
Angus Konge2f4c032013-12-19 10:24:33 -0800438 }
439 return updatedList;
440 }
441
442 @Override
443 protected void onPostExecute(List<Integer> updatedData) {
444 if (mLocalDataListener == null) {
445 return;
446 }
447 mLocalDataListener.onMetadataUpdated(updatedData);
448 }
449 }
Angus Kong49b9ba22013-05-13 13:42:21 -0700450}