CameraDataAdapter.java revision a16e7b50f3148f581439509279f242092e254309
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
17package com.android.camera.data;
18
19import android.content.ContentResolver;
20import android.content.Context;
21import android.database.Cursor;
22import android.graphics.drawable.Drawable;
23import android.net.Uri;
24import android.os.AsyncTask;
25import android.provider.MediaStore;
26import android.util.Log;
27import android.view.View;
28
29import com.android.camera.Storage;
30import com.android.camera.ui.FilmStripView.ImageData;
31
32import java.util.ArrayList;
33import java.util.Collections;
34import java.util.Comparator;
35import java.util.List;
36
37/**
38 * A {@link LocalDataAdapter} that provides data in the camera folder.
39 */
40public class CameraDataAdapter implements LocalDataAdapter {
41    private static final String TAG = "CAM_CameraDataAdapter";
42
43    private static final int DEFAULT_DECODE_SIZE = 1600;
44    private static final String[] CAMERA_PATH = { Storage.DIRECTORY + "%" };
45
46    private List<LocalData> mImages;
47
48    private Listener mListener;
49    private Drawable mPlaceHolder;
50
51    private int mSuggestedWidth = DEFAULT_DECODE_SIZE;
52    private int mSuggestedHeight = DEFAULT_DECODE_SIZE;
53
54    private LocalData mLocalDataToDelete;
55
56    public CameraDataAdapter(Drawable placeHolder) {
57        mImages = new ArrayList<LocalData>();
58        mPlaceHolder = placeHolder;
59    }
60
61    @Override
62    public void requestLoad(ContentResolver resolver) {
63        QueryTask qtask = new QueryTask();
64        qtask.execute(resolver);
65    }
66
67    @Override
68    public LocalData getLocalData(int dataID) {
69        if (dataID < 0 || dataID >= mImages.size()) {
70            return null;
71        }
72
73        return mImages.get(dataID);
74    }
75
76    @Override
77    public int getTotalNumber() {
78        return mImages.size();
79    }
80
81    @Override
82    public ImageData getImageData(int id) {
83        return getLocalData(id);
84    }
85
86    @Override
87    public void suggestViewSizeBound(int w, int h) {
88        if (w <= 0 || h <= 0) {
89            mSuggestedWidth  = mSuggestedHeight = DEFAULT_DECODE_SIZE;
90        } else {
91            mSuggestedWidth = (w < DEFAULT_DECODE_SIZE ? w : DEFAULT_DECODE_SIZE);
92            mSuggestedHeight = (h < DEFAULT_DECODE_SIZE ? h : DEFAULT_DECODE_SIZE);
93        }
94    }
95
96    @Override
97    public View getView(Context c, int dataID) {
98        if (dataID >= mImages.size() || dataID < 0) {
99            return null;
100        }
101
102        return mImages.get(dataID).getView(
103                c, mSuggestedWidth, mSuggestedHeight,
104                mPlaceHolder.getConstantState().newDrawable());
105    }
106
107    @Override
108    public void setListener(Listener listener) {
109        mListener = listener;
110        if (mImages != null) {
111            mListener.onDataLoaded();
112        }
113    }
114
115    @Override
116    public boolean canSwipeInFullScreen(int dataID) {
117        if (dataID < mImages.size() && dataID > 0) {
118            return mImages.get(dataID).canSwipeInFullScreen();
119        }
120        return true;
121    }
122
123    @Override
124    public void removeData(Context c, int dataID) {
125        if (dataID >= mImages.size()) return;
126        LocalData d = mImages.remove(dataID);
127        // Delete previously removed data first.
128        executeDeletion(c);
129        mLocalDataToDelete = d;
130        mListener.onDataRemoved(dataID, d);
131    }
132
133    // TODO: put the database query on background thread
134    @Override
135    public void addNewVideo(ContentResolver cr, Uri uri) {
136        Cursor c = cr.query(uri,
137                LocalMediaData.VideoData.QUERY_PROJECTION,
138                MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH,
139                LocalMediaData.VideoData.QUERY_ORDER);
140        if (c == null || !c.moveToFirst()) {
141            return;
142        }
143        int pos = findDataByContentUri(uri);
144        LocalMediaData.VideoData newData = LocalMediaData.VideoData.buildFromCursor(c);
145        if (pos != -1) {
146            // A duplicate one, just do a substitute.
147            updateData(pos, newData);
148        } else {
149            // A new data.
150            insertData(newData);
151        }
152    }
153
154    // TODO: put the database query on background thread
155    @Override
156    public void addNewPhoto(ContentResolver cr, Uri uri) {
157        Cursor c = cr.query(uri,
158                LocalMediaData.PhotoData.QUERY_PROJECTION,
159                MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH,
160                LocalMediaData.PhotoData.QUERY_ORDER);
161        if (c == null || !c.moveToFirst()) {
162            return;
163        }
164        int pos = findDataByContentUri(uri);
165        LocalMediaData.PhotoData newData = LocalMediaData.PhotoData.buildFromCursor(c);
166        if (pos != -1) {
167            // a duplicate one, just do a substitute.
168            Log.v(TAG, "found duplicate photo");
169            updateData(pos, newData);
170        } else {
171            // a new data.
172            insertData(newData);
173        }
174    }
175
176    @Override
177    public int findDataByContentUri(Uri uri) {
178        for (int i = 0; i < mImages.size(); i++) {
179            Uri u = mImages.get(i).getContentUri();
180            if (u == null) {
181                continue;
182            }
183            if (u.equals(uri)) {
184                return i;
185            }
186        }
187        return -1;
188    }
189
190    @Override
191    public boolean undoDataRemoval() {
192        if (mLocalDataToDelete == null) return false;
193        LocalData d = mLocalDataToDelete;
194        mLocalDataToDelete = null;
195        insertData(d);
196        return true;
197    }
198
199    @Override
200    public boolean executeDeletion(Context c) {
201        if (mLocalDataToDelete == null) return false;
202
203        DeletionTask task = new DeletionTask(c);
204        task.execute(mLocalDataToDelete);
205        mLocalDataToDelete = null;
206        return true;
207    }
208
209    @Override
210    public void flush() {
211        replaceData(new ArrayList<LocalData>());
212    }
213
214    @Override
215    public void refresh(ContentResolver resolver, Uri contentUri) {
216        int pos = findDataByContentUri(contentUri);
217        if (pos == -1) {
218            return;
219        }
220
221        LocalData data = mImages.get(pos);
222        LocalData refreshedData = data.refresh(resolver);
223        if (refreshedData != null) {
224            updateData(pos, refreshedData);
225        }
226    }
227
228    @Override
229    public void updateData(final int pos, LocalData data) {
230        mImages.set(pos, data);
231        if (mListener != null) {
232            mListener.onDataUpdated(new UpdateReporter() {
233                @Override
234                public boolean isDataRemoved(int dataID) {
235                    return false;
236                }
237
238                @Override
239                public boolean isDataUpdated(int dataID) {
240                    return (dataID == pos);
241                }
242            });
243        }
244    }
245
246    @Override
247    public void insertData(LocalData data) {
248        // Since this function is mostly for adding the newest data,
249        // a simple linear search should yield the best performance over a
250        // binary search.
251        int pos = 0;
252        Comparator<LocalData> comp = new LocalData.NewestFirstComparator();
253        for (; pos < mImages.size()
254                && comp.compare(data, mImages.get(pos)) > 0; pos++);
255        mImages.add(pos, data);
256        if (mListener != null) {
257            mListener.onDataInserted(pos, data);
258        }
259    }
260
261    /** Update all the data */
262    private void replaceData(List<LocalData> list) {
263        if (list.size() == 0 && mImages.size() == 0) {
264            return;
265        }
266        mImages = list;
267        if (mListener != null) {
268            mListener.onDataLoaded();
269        }
270    }
271
272    private class QueryTask extends AsyncTask<ContentResolver, Void, List<LocalData>> {
273
274        /**
275         * Loads all the photo and video data in the camera folder in background
276         * and combine them into one single list.
277         *
278         * @param resolver {@link ContentResolver} to load all the data.
279         * @return An {@link ArrayList} of all loaded data.
280         */
281        @Override
282        protected List<LocalData> doInBackground(ContentResolver... resolver) {
283            List<LocalData> l = new ArrayList<LocalData>();
284            // Photos
285            Cursor c = resolver[0].query(
286                    LocalMediaData.PhotoData.CONTENT_URI,
287                    LocalMediaData.PhotoData.QUERY_PROJECTION,
288                    MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH,
289                    LocalMediaData.PhotoData.QUERY_ORDER);
290            if (c != null && c.moveToFirst()) {
291                // build up the list.
292                while (true) {
293                    LocalData data = LocalMediaData.PhotoData.buildFromCursor(c);
294                    if (data != null) {
295                        l.add(data);
296                    } else {
297                        Log.e(TAG, "Error loading data:"
298                                + c.getString(LocalMediaData.PhotoData.COL_DATA));
299                    }
300                    if (c.isLast()) {
301                        break;
302                    }
303                    c.moveToNext();
304                }
305            }
306            if (c != null) {
307                c.close();
308            }
309
310            c = resolver[0].query(
311                    LocalMediaData.VideoData.CONTENT_URI,
312                    LocalMediaData.VideoData.QUERY_PROJECTION,
313                    MediaStore.Video.Media.DATA + " like ? ", CAMERA_PATH,
314                    LocalMediaData.VideoData.QUERY_ORDER);
315            if (c != null && c.moveToFirst()) {
316                // build up the list.
317                c.moveToFirst();
318                while (true) {
319                    LocalData data = LocalMediaData.VideoData.buildFromCursor(c);
320                    if (data != null) {
321                        l.add(data);
322                    } else {
323                        Log.e(TAG, "Error loading data:"
324                                + c.getString(LocalMediaData.VideoData.COL_DATA));
325                    }
326                    if (!c.isLast()) {
327                        c.moveToNext();
328                    } else {
329                        break;
330                    }
331                }
332            }
333            if (c != null) {
334                c.close();
335            }
336
337            if (l.size() != 0) {
338                Collections.sort(l, new LocalData.NewestFirstComparator());
339            }
340
341            return l;
342        }
343
344        @Override
345        protected void onPostExecute(List<LocalData> l) {
346            replaceData(l);
347        }
348    }
349
350    private class DeletionTask extends AsyncTask<LocalData, Void, Void> {
351        Context mContext;
352
353        DeletionTask(Context context) {
354            mContext = context;
355        }
356
357        @Override
358        protected Void doInBackground(LocalData... data) {
359            for (int i = 0; i < data.length; i++) {
360                if (!data[i].isDataActionSupported(LocalData.ACTION_DELETE)) {
361                    Log.v(TAG, "Deletion is not supported:" + data[i]);
362                    continue;
363                }
364                data[i].delete(mContext);
365            }
366            return null;
367        }
368    }
369}
370