OSDN Git Service

07100efbc74227f24e984a7553f983119b5a3dc2
[android-x86/packages-apps-Camera2.git] / src / com / android / camera / data / CameraDataAdapter.java
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
19 import android.content.ContentResolver;
20 import android.content.Context;
21 import android.net.Uri;
22 import android.os.AsyncTask;
23 import android.view.View;
24
25 import com.android.camera.Storage;
26 import com.android.camera.data.LocalData.ActionCallback;
27 import com.android.camera.debug.Log;
28 import com.android.camera.filmstrip.ImageData;
29 import com.android.camera.util.Callback;
30
31 import java.util.ArrayList;
32 import java.util.Comparator;
33 import java.util.List;
34
35 /**
36  * A {@link LocalDataAdapter} that provides data in the camera folder.
37  */
38 public class CameraDataAdapter implements LocalDataAdapter {
39     private static final Log.Tag TAG = new Log.Tag("CameraDataAdapter");
40
41     private static final int DEFAULT_DECODE_SIZE = 1600;
42
43     private final Context mContext;
44
45     private LocalDataList mImages;
46
47     private Listener mListener;
48     private LocalDataListener mLocalDataListener;
49     private final int mPlaceHolderResourceId;
50
51     private int mSuggestedWidth = DEFAULT_DECODE_SIZE;
52     private int mSuggestedHeight = DEFAULT_DECODE_SIZE;
53     private long mLastPhotoId = LocalMediaData.QUERY_ALL_MEDIA_ID;
54
55     private LocalData mLocalDataToDelete;
56
57     public CameraDataAdapter(Context context, int placeholderResource) {
58         mContext = context;
59         mImages = new LocalDataList();
60         mPlaceHolderResourceId = placeholderResource;
61     }
62
63     @Override
64     public void setLocalDataListener(LocalDataListener listener) {
65         mLocalDataListener = listener;
66     }
67
68     @Override
69     public void requestLoadNewPhotos() {
70         LoadNewPhotosTask ltask = new LoadNewPhotosTask(mLastPhotoId);
71         ltask.execute(mContext.getContentResolver());
72     }
73
74     @Override
75     public void requestLoad(Callback<Void> doneCallback) {
76         QueryTask qtask = new QueryTask(doneCallback);
77         qtask.execute(mContext);
78     }
79
80     @Override
81     public AsyncTask updateMetadata(int dataId) {
82         MetadataUpdateTask result = new MetadataUpdateTask();
83         result.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, dataId);
84         return result;
85     }
86
87     @Override
88     public boolean isMetadataUpdated(int dataId) {
89         if (dataId < 0 || dataId >= mImages.size()) {
90             return true;
91         }
92         return mImages.get(dataId).isMetadataUpdated();
93     }
94
95     @Override
96     public int getItemViewType(int dataId) {
97         if (dataId < 0 || dataId >= mImages.size()) {
98             return -1;
99         }
100
101         return mImages.get(dataId).getItemViewType().ordinal();
102     }
103
104     @Override
105     public LocalData getLocalData(int dataID) {
106         if (dataID < 0 || dataID >= mImages.size()) {
107             return null;
108         }
109         return mImages.get(dataID);
110     }
111
112     @Override
113     public int getTotalNumber() {
114         return mImages.size();
115     }
116
117     @Override
118     public ImageData getImageData(int id) {
119         return getLocalData(id);
120     }
121
122     @Override
123     public void suggestViewSizeBound(int w, int h) {
124         mSuggestedWidth = w;
125         mSuggestedHeight = h;
126     }
127
128     @Override
129     public View getView(Context context, View recycled, int dataID,
130             ActionCallback actionCallback) {
131         if (dataID >= mImages.size() || dataID < 0) {
132             return null;
133         }
134
135         return mImages.get(dataID).getView(
136                 context, recycled, mSuggestedWidth, mSuggestedHeight,
137                 mPlaceHolderResourceId, this, /* inProgress */ false, actionCallback);
138     }
139
140     @Override
141     public void resizeView(Context context, int dataID, View view, int w, int h) {
142         if (dataID >= mImages.size() || dataID < 0) {
143             return;
144         }
145         mImages.get(dataID).loadFullImage(context, mSuggestedWidth, mSuggestedHeight, view, this);
146     }
147
148     @Override
149     public void setListener(Listener listener) {
150         mListener = listener;
151         if (mImages.size() != 0) {
152             mListener.onDataLoaded();
153         }
154     }
155
156     @Override
157     public boolean canSwipeInFullScreen(int dataID) {
158         if (dataID < mImages.size() && dataID > 0) {
159             return mImages.get(dataID).canSwipeInFullScreen();
160         }
161         return true;
162     }
163
164     @Override
165     public void removeData(int dataID) {
166         LocalData d = mImages.remove(dataID);
167         if (d == null) {
168             return;
169         }
170
171         // Delete previously removed data first.
172         executeDeletion();
173         mLocalDataToDelete = d;
174         mListener.onDataRemoved(dataID, d);
175     }
176
177     @Override
178     public boolean addData(LocalData newData) {
179         final Uri uri = newData.getUri();
180         int pos = findDataByContentUri(uri);
181         if (pos != -1) {
182             // a duplicate one, just do a substitute.
183             Log.v(TAG, "found duplicate data");
184             updateData(pos, newData);
185             return false;
186         } else {
187             // a new data.
188             insertData(newData);
189             return true;
190         }
191     }
192
193     @Override
194     public int findDataByContentUri(Uri uri) {
195         // LocalDataList will return in O(1) if the uri is not contained.
196         // Otherwise the performance is O(n), but this is acceptable as we will
197         // most often call this to find an element at the beginning of the list.
198         return mImages.indexOf(uri);
199     }
200
201     @Override
202     public boolean undoDataRemoval() {
203         if (mLocalDataToDelete == null) {
204             return false;
205         }
206         LocalData d = mLocalDataToDelete;
207         mLocalDataToDelete = null;
208         insertData(d);
209         return true;
210     }
211
212     @Override
213     public boolean executeDeletion() {
214         if (mLocalDataToDelete == null) {
215             return false;
216         }
217
218         DeletionTask task = new DeletionTask();
219         task.execute(mLocalDataToDelete);
220         mLocalDataToDelete = null;
221         return true;
222     }
223
224     @Override
225     public void flush() {
226         replaceData(new LocalDataList());
227     }
228
229     @Override
230     public void refresh(Uri uri) {
231         final int pos = findDataByContentUri(uri);
232         if (pos == -1) {
233             return;
234         }
235
236         LocalData data = mImages.get(pos);
237         LocalData refreshedData = data.refresh(mContext);
238
239         // Refresh failed. Probably removed already.
240         if (refreshedData == null && mListener != null) {
241             mListener.onDataRemoved(pos, data);
242             return;
243         }
244         updateData(pos, refreshedData);
245     }
246
247     @Override
248     public void updateData(final int pos, LocalData data) {
249         mImages.set(pos, data);
250         if (mListener != null) {
251             mListener.onDataUpdated(new UpdateReporter() {
252                 @Override
253                 public boolean isDataRemoved(int dataID) {
254                     return false;
255                 }
256
257                 @Override
258                 public boolean isDataUpdated(int dataID) {
259                     return (dataID == pos);
260                 }
261             });
262         }
263     }
264
265     private void insertData(LocalData data) {
266         // Since this function is mostly for adding the newest data,
267         // a simple linear search should yield the best performance over a
268         // binary search.
269         int pos = 0;
270         Comparator<LocalData> comp = new LocalData.NewestFirstComparator();
271         for (; pos < mImages.size()
272                 && comp.compare(data, mImages.get(pos)) > 0; pos++) {
273             ;
274         }
275         mImages.add(pos, data);
276         if (mListener != null) {
277             mListener.onDataInserted(pos, data);
278         }
279     }
280
281     /** Update all the data */
282     private void replaceData(LocalDataList list) {
283         if (list.size() == 0 && mImages.size() == 0) {
284             return;
285         }
286         mImages = list;
287         if (mListener != null) {
288             mListener.onDataLoaded();
289         }
290     }
291
292     @Override
293     public List<AsyncTask> preloadItems(List<Integer> items) {
294         List<AsyncTask> result = new ArrayList<AsyncTask>();
295         for (Integer id : items) {
296             if (!isMetadataUpdated(id)) {
297                 result.add(updateMetadata(id));
298             }
299         }
300         return result;
301     }
302
303     @Override
304     public void cancelItems(List<AsyncTask> loadTokens) {
305         for (AsyncTask asyncTask : loadTokens) {
306             if (asyncTask != null) {
307                 asyncTask.cancel(false);
308             }
309         }
310     }
311
312     @Override
313     public List<Integer> getItemsInRange(int startPosition, int endPosition) {
314         List<Integer> result = new ArrayList<Integer>();
315         for (int i = Math.max(0, startPosition); i < endPosition; i++) {
316             result.add(i);
317         }
318         return result;
319     }
320
321     @Override
322     public int getCount() {
323         return getTotalNumber();
324     }
325
326     private class LoadNewPhotosTask extends AsyncTask<ContentResolver, Void, List<LocalData>> {
327
328         private final long mMinPhotoId;
329
330         public LoadNewPhotosTask(long lastPhotoId) {
331             mMinPhotoId = lastPhotoId;
332         }
333
334         /**
335          * Loads any new photos added to our storage directory since our last query.
336          * @param contentResolvers {@link android.content.ContentResolver} to load data.
337          * @return An {@link java.util.ArrayList} containing any new data.
338          */
339         @Override
340         protected List<LocalData> doInBackground(ContentResolver... contentResolvers) {
341             if (mMinPhotoId != LocalMediaData.QUERY_ALL_MEDIA_ID) {
342                 final ContentResolver cr = contentResolvers[0];
343                 return LocalMediaData.PhotoData.query(cr, LocalMediaData.PhotoData.CONTENT_URI,
344                         mMinPhotoId);
345             }
346             return new ArrayList<LocalData>(0);
347         }
348
349         @Override
350         protected void onPostExecute(List<LocalData> newPhotoData) {
351             if (!newPhotoData.isEmpty()) {
352                 LocalData newestPhoto = newPhotoData.get(0);
353                 // We may overlap with another load task or a query task, in which case we want
354                 // to be sure we never decrement the oldest seen id.
355                 mLastPhotoId = Math.max(mLastPhotoId, newestPhoto.getContentId());
356             }
357             // We may add data that is already present, but if we do, it will be deduped in addData.
358             // addData does not dedupe session items, so we ignore them here
359             for (LocalData localData : newPhotoData) {
360                 Uri sessionUri = Storage.getSessionUriFromContentUri(localData.getUri());
361                 if (sessionUri == null) {
362                     addData(localData);
363                 }
364             }
365         }
366     }
367
368     private class QueryTaskResult {
369         public LocalDataList mLocalDataList;
370         public long mLastPhotoId;
371
372         public QueryTaskResult(LocalDataList localDataList, long lastPhotoId) {
373             mLocalDataList = localDataList;
374             mLastPhotoId = lastPhotoId;
375         }
376     }
377
378     private class QueryTask extends AsyncTask<Context, Void, QueryTaskResult> {
379         // The maximum number of data to load metadata for in a single task.
380         private static final int MAX_METADATA = 5;
381
382         private final Callback<Void> mDoneCallback;
383
384         public QueryTask(Callback<Void> doneCallback) {
385             mDoneCallback = doneCallback;
386         }
387
388         /**
389          * Loads all the photo and video data in the camera folder in background
390          * and combine them into one single list.
391          *
392          * @param contexts {@link Context} to load all the data.
393          * @return An {@link com.android.camera.data.CameraDataAdapter.QueryTaskResult} containing
394          *  all loaded data and the highest photo id in the dataset.
395          */
396         @Override
397         protected QueryTaskResult doInBackground(Context... contexts) {
398             final Context context = contexts[0];
399             final ContentResolver cr = context.getContentResolver();
400             LocalDataList l = new LocalDataList();
401             // Photos
402             List<LocalData> photoData = LocalMediaData.PhotoData.query(cr,
403                     LocalMediaData.PhotoData.CONTENT_URI, LocalMediaData.QUERY_ALL_MEDIA_ID);
404             List<LocalData> videoData = LocalMediaData.VideoData.query(cr,
405                     LocalMediaData.VideoData.CONTENT_URI, LocalMediaData.QUERY_ALL_MEDIA_ID);
406
407             long lastPhotoId = LocalMediaData.QUERY_ALL_MEDIA_ID;
408             if (!photoData.isEmpty()) {
409                 lastPhotoId = photoData.get(0).getContentId();
410             }
411
412             l.addAll(photoData);
413             l.addAll(videoData);
414             l.sort(new LocalData.NewestFirstComparator());
415
416             // Load enough metadata so it's already loaded when we open the filmstrip.
417             for (int i = 0; i < MAX_METADATA && i < l.size(); i++) {
418                 LocalData data = l.get(i);
419                 MetadataLoader.loadMetadata(context, data);
420             }
421             return new QueryTaskResult(l, lastPhotoId);
422         }
423
424         @Override
425         protected void onPostExecute(QueryTaskResult result) {
426             // Since we're wiping away all of our data, we should always replace any existing last
427             // photo id with the new one we just obtained so it matches the data we're showing.
428             mLastPhotoId = result.mLastPhotoId;
429             replaceData(result.mLocalDataList);
430             if (mDoneCallback != null) {
431                 mDoneCallback.onCallback(null);
432             }
433             // Now check for any photos added since this task was kicked off
434             LoadNewPhotosTask ltask = new LoadNewPhotosTask(mLastPhotoId);
435             ltask.execute(mContext.getContentResolver());
436         }
437     }
438
439     private class DeletionTask extends AsyncTask<LocalData, Void, Void> {
440         @Override
441         protected Void doInBackground(LocalData... data) {
442             for (int i = 0; i < data.length; i++) {
443                 if (!data[i].isDataActionSupported(LocalData.DATA_ACTION_DELETE)) {
444                     Log.v(TAG, "Deletion is not supported:" + data[i]);
445                     continue;
446                 }
447                 data[i].delete(mContext);
448             }
449             return null;
450         }
451     }
452
453     private class MetadataUpdateTask extends AsyncTask<Integer, Void, List<Integer> > {
454         @Override
455         protected List<Integer> doInBackground(Integer... dataId) {
456             List<Integer> updatedList = new ArrayList<Integer>();
457             for (Integer id : dataId) {
458                 if (id < 0 || id >= mImages.size()) {
459                     continue;
460                 }
461                 final LocalData data = mImages.get(id);
462                 if (MetadataLoader.loadMetadata(mContext, data)) {
463                     updatedList.add(id);
464                 }
465             }
466             return updatedList;
467         }
468
469         @Override
470         protected void onPostExecute(final List<Integer> updatedData) {
471             // Since the metadata will affect the width and height of the data
472             // if it's a video, we need to notify the DataAdapter listener
473             // because ImageData.getWidth() and ImageData.getHeight() now may
474             // return different values due to the metadata.
475             if (mListener != null) {
476                 mListener.onDataUpdated(new UpdateReporter() {
477                     @Override
478                     public boolean isDataRemoved(int dataID) {
479                         return false;
480                     }
481
482                     @Override
483                     public boolean isDataUpdated(int dataID) {
484                         return updatedData.contains(dataID);
485                     }
486                 });
487             }
488             if (mLocalDataListener == null) {
489                 return;
490             }
491             mLocalDataListener.onMetadataUpdated(updatedData);
492         }
493     }
494 }