blob: 2cdb2c172fc389c29c281b7ea8107870979c1310 [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.camera.data;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import com.android.camera.Storage;
import com.android.camera.ui.FilmStripView.ImageData;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* A {@link LocalDataAdapter} that provides data in the camera folder.
*/
public class CameraDataAdapter implements LocalDataAdapter {
private static final String TAG = "CAM_CameraDataAdapter";
private static final int DEFAULT_DECODE_SIZE = 3000;
private static final String[] CAMERA_PATH = { Storage.DIRECTORY + "%" };
private List<LocalData> mImages;
private Listener mListener;
private Drawable mPlaceHolder;
private int mSuggestedWidth = DEFAULT_DECODE_SIZE;
private int mSuggestedHeight = DEFAULT_DECODE_SIZE;
private LocalData mLocalDataToDelete;
public CameraDataAdapter(Drawable placeHolder) {
mImages = new ArrayList<LocalData>();
mPlaceHolder = placeHolder;
}
@Override
public void requestLoad(ContentResolver resolver) {
QueryTask qtask = new QueryTask();
qtask.execute(resolver);
}
@Override
public LocalData getLocalData(int dataID) {
if (dataID < 0 || dataID >= mImages.size()) {
return null;
}
return mImages.get(dataID);
}
@Override
public int getTotalNumber() {
return mImages.size();
}
@Override
public ImageData getImageData(int id) {
return getLocalData(id);
}
@Override
public void suggestViewSizeBound(int w, int h) {
if (w <= 0 || h <= 0) {
mSuggestedWidth = mSuggestedHeight = DEFAULT_DECODE_SIZE;
} else {
mSuggestedWidth = (w < DEFAULT_DECODE_SIZE ? w : DEFAULT_DECODE_SIZE);
mSuggestedHeight = (h < DEFAULT_DECODE_SIZE ? h : DEFAULT_DECODE_SIZE);
}
}
@Override
public View getView(Context c, int dataID) {
if (dataID >= mImages.size() || dataID < 0) {
return null;
}
return mImages.get(dataID).getView(
c, mSuggestedWidth, mSuggestedHeight,
mPlaceHolder.getConstantState().newDrawable());
}
@Override
public void setListener(Listener listener) {
mListener = listener;
if (mImages != null) {
mListener.onDataLoaded();
}
}
@Override
public void onDataFullScreen(int dataID, boolean fullScreen) {
if (dataID < mImages.size() && dataID >= 0) {
mImages.get(dataID).onFullScreen(fullScreen);
}
}
@Override
public boolean canSwipeInFullScreen(int dataID) {
if (dataID < mImages.size() && dataID > 0) {
return mImages.get(dataID).canSwipeInFullScreen();
}
return true;
}
@Override
public void removeData(Context c, int dataID) {
if (dataID >= mImages.size()) return;
LocalData d = mImages.remove(dataID);
// Delete previously removed data first.
executeDeletion(c);
mLocalDataToDelete = d;
mListener.onDataRemoved(dataID, d);
}
// TODO: put the database query on background thread
@Override
public void addNewVideo(ContentResolver cr, Uri uri) {
Cursor c = cr.query(uri,
LocalMediaData.VideoData.QUERY_PROJECTION,
MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH,
LocalMediaData.VideoData.QUERY_ORDER);
if (c == null || !c.moveToFirst()) {
return;
}
int pos = findDataByContentUri(uri);
LocalMediaData.VideoData newData = LocalMediaData.VideoData.buildFromCursor(c);
if (pos != -1) {
// A duplicate one, just do a substitute.
updateData(pos, newData);
} else {
// A new data.
insertData(newData);
}
}
// TODO: put the database query on background thread
@Override
public void addNewPhoto(ContentResolver cr, Uri uri) {
Cursor c = cr.query(uri,
LocalMediaData.PhotoData.QUERY_PROJECTION,
MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH,
LocalMediaData.PhotoData.QUERY_ORDER);
if (c == null || !c.moveToFirst()) {
return;
}
int pos = findDataByContentUri(uri);
LocalMediaData.PhotoData newData = LocalMediaData.PhotoData.buildFromCursor(c);
if (pos != -1) {
// a duplicate one, just do a substitute.
Log.v(TAG, "found duplicate photo");
updateData(pos, newData);
} else {
// a new data.
insertData(newData);
}
}
@Override
public int findDataByContentUri(Uri uri) {
for (int i = 0; i < mImages.size(); i++) {
Uri u = mImages.get(i).getContentUri();
if (u == null) {
continue;
}
if (u.equals(uri)) {
return i;
}
}
return -1;
}
@Override
public boolean undoDataRemoval() {
if (mLocalDataToDelete == null) return false;
LocalData d = mLocalDataToDelete;
mLocalDataToDelete = null;
insertData(d);
return true;
}
@Override
public boolean executeDeletion(Context c) {
if (mLocalDataToDelete == null) return false;
DeletionTask task = new DeletionTask(c);
task.execute(mLocalDataToDelete);
mLocalDataToDelete = null;
return true;
}
@Override
public void flush() {
replaceData(new ArrayList<LocalData>());
}
@Override
public void refresh(ContentResolver resolver, Uri contentUri) {
int pos = findDataByContentUri(contentUri);
if (pos == -1) {
return;
}
LocalData data = mImages.get(pos);
if (data.refresh(resolver)) {
updateData(pos, data);
}
}
@Override
public void updateData(final int pos, LocalData data) {
mImages.set(pos, data);
if (mListener != null) {
mListener.onDataUpdated(new UpdateReporter() {
@Override
public boolean isDataRemoved(int dataID) {
return false;
}
@Override
public boolean isDataUpdated(int dataID) {
return (dataID == pos);
}
});
}
}
@Override
public void insertData(LocalData data) {
// Since this function is mostly for adding the newest data,
// a simple linear search should yield the best performance over a
// binary search.
int pos = 0;
Comparator<LocalData> comp = new LocalData.NewestFirstComparator();
for (; pos < mImages.size()
&& comp.compare(data, mImages.get(pos)) > 0; pos++);
mImages.add(pos, data);
if (mListener != null) {
mListener.onDataInserted(pos, data);
}
}
/** Update all the data */
private void replaceData(List<LocalData> list) {
if (list.size() == 0 && mImages.size() == 0) {
return;
}
mImages = list;
if (mListener != null) {
mListener.onDataLoaded();
}
}
private class QueryTask extends AsyncTask<ContentResolver, Void, List<LocalData>> {
/**
* Loads all the photo and video data in the camera folder in background
* and combine them into one single list.
*
* @param resolver {@link ContentResolver} to load all the data.
* @return An {@link ArrayList} of all loaded data.
*/
@Override
protected List<LocalData> doInBackground(ContentResolver... resolver) {
List<LocalData> l = new ArrayList<LocalData>();
// Photos
Cursor c = resolver[0].query(
LocalMediaData.PhotoData.CONTENT_URI,
LocalMediaData.PhotoData.QUERY_PROJECTION,
MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH,
LocalMediaData.PhotoData.QUERY_ORDER);
if (c != null && c.moveToFirst()) {
// build up the list.
while (true) {
LocalData data = LocalMediaData.PhotoData.buildFromCursor(c);
if (data != null) {
l.add(data);
} else {
Log.e(TAG, "Error loading data:"
+ c.getString(LocalMediaData.PhotoData.COL_DATA));
}
if (c.isLast()) {
break;
}
c.moveToNext();
}
}
if (c != null) {
c.close();
}
c = resolver[0].query(
LocalMediaData.VideoData.CONTENT_URI,
LocalMediaData.VideoData.QUERY_PROJECTION,
MediaStore.Video.Media.DATA + " like ? ", CAMERA_PATH,
LocalMediaData.VideoData.QUERY_ORDER);
if (c != null && c.moveToFirst()) {
// build up the list.
c.moveToFirst();
while (true) {
LocalData data = LocalMediaData.VideoData.buildFromCursor(c);
if (data != null) {
l.add(data);
} else {
Log.e(TAG, "Error loading data:"
+ c.getString(LocalMediaData.VideoData.COL_DATA));
}
if (!c.isLast()) {
c.moveToNext();
} else {
break;
}
}
}
if (c != null) {
c.close();
}
if (l.size() != 0) {
Collections.sort(l, new LocalData.NewestFirstComparator());
}
return l;
}
@Override
protected void onPostExecute(List<LocalData> l) {
replaceData(l);
}
}
private class DeletionTask extends AsyncTask<LocalData, Void, Void> {
Context mContext;
DeletionTask(Context context) {
mContext = context;
}
@Override
protected Void doInBackground(LocalData... data) {
for (int i = 0; i < data.length; i++) {
if (!data[i].isDataActionSupported(LocalData.ACTION_DELETE)) {
Log.v(TAG, "Deletion is not supported:" + data[i]);
continue;
}
data[i].delete(mContext);
}
return null;
}
}
}