| /* |
| * 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; |
| import com.android.camera.ui.FilmStripView.ImageData; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.List; |
| |
| /** |
| * A FilmStrip.DataProvider that provide data in the camera folder. |
| * |
| * The given view for camera preview won't be added until the preview info |
| * has been set by setCameraPreviewInfo(int, int). |
| */ |
| public class CameraDataAdapter implements FilmStripView.DataAdapter { |
| private static final String TAG = CameraDataAdapter.class.getSimpleName(); |
| |
| private static final int DEFAULT_DECODE_SIZE = 3000; |
| private static final String[] CAMERA_PATH = { Storage.DIRECTORY + "%" }; |
| |
| private List<LocalData> mImages; |
| |
| private Listener mListener; |
| private View mCameraPreviewView; |
| private Drawable mPlaceHolder; |
| |
| private int mSuggestedWidth = DEFAULT_DECODE_SIZE; |
| private int mSuggestedHeight = DEFAULT_DECODE_SIZE; |
| |
| public CameraDataAdapter(Drawable placeHolder) { |
| mPlaceHolder = placeHolder; |
| } |
| |
| public void setCameraPreviewInfo(View cameraPreview, int width, int height) { |
| mCameraPreviewView = cameraPreview; |
| addOrReplaceCameraData(buildCameraImageData(width, height)); |
| } |
| |
| public void requestLoad(ContentResolver resolver) { |
| QueryTask qtask = new QueryTask(); |
| qtask.execute(resolver); |
| } |
| |
| @Override |
| public int getTotalNumber() { |
| if (mImages == null) { |
| return 0; |
| } |
| return mImages.size(); |
| } |
| |
| @Override |
| public ImageData getImageData(int id) { |
| if (mImages == null || id >= mImages.size() || id < 0) { |
| return null; |
| } |
| return mImages.get(id); |
| } |
| |
| @Override |
| public void suggestDecodeSize(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 (mImages == null) { |
| return null; |
| } |
| 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(); |
| } |
| } |
| |
| public void removeData(int dataID) { |
| if (dataID >= mImages.size()) return; |
| LocalData d = mImages.remove(dataID); |
| mListener.onDataRemoved(dataID, d); |
| } |
| |
| private void insertData(LocalData data) { |
| if (mImages == null) { |
| mImages = new ArrayList<LocalData>(); |
| } |
| |
| // 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); |
| } |
| } |
| |
| public void addNewVideo(ContentResolver cr, Uri uri) { |
| Cursor c = cr.query(uri, |
| LocalData.Video.QUERY_PROJECTION, |
| MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH, |
| LocalData.Video.QUERY_ORDER); |
| if (c != null && c.moveToFirst()) { |
| insertData(LocalData.Video.buildFromCursor(c)); |
| } |
| } |
| |
| public void addNewPhoto(ContentResolver cr, Uri uri) { |
| Cursor c = cr.query(uri, |
| LocalData.Photo.QUERY_PROJECTION, |
| MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH, |
| LocalData.Photo.QUERY_ORDER); |
| if (c != null && c.moveToFirst()) { |
| insertData(LocalData.Photo.buildFromCursor(c)); |
| } |
| } |
| |
| // Update all the data but keep the camera data if already set. |
| private void replaceData(List<LocalData> list) { |
| boolean changed = (list != mImages); |
| LocalData cameraData = null; |
| if (mImages != null && mImages.size() > 0) { |
| cameraData = mImages.get(0); |
| if (cameraData.getType() != ImageData.TYPE_CAMERA_PREVIEW) { |
| cameraData = null; |
| } |
| } |
| |
| mImages = list; |
| if (cameraData != null) { |
| // camera view exists, so we make sure at least 1 data is in the list. |
| if (mImages == null) { |
| mImages = new ArrayList<LocalData>(); |
| } |
| mImages.add(0, cameraData); |
| if (mListener != null) { |
| // Only the camera data is not changed, everything else is changed. |
| mListener.onDataUpdated(new UpdateReporter() { |
| @Override |
| public boolean isDataRemoved(int id) { |
| return false; |
| } |
| |
| @Override |
| public boolean isDataUpdated(int id) { |
| if (id == 0) return false; |
| return true; |
| } |
| }); |
| } |
| } else { |
| // both might be null. |
| if (changed) { |
| mListener.onDataLoaded(); |
| } |
| } |
| } |
| |
| public void flush() { |
| replaceData(null); |
| } |
| |
| public void addLocalData(LocalData data) { |
| insertData(data); |
| } |
| |
| private LocalData buildCameraImageData(int width, int height) { |
| LocalData d = new CameraPreviewData(width, height); |
| return d; |
| } |
| |
| private void addOrReplaceCameraData(LocalData data) { |
| if (mImages == null) { |
| mImages = new ArrayList<LocalData>(); |
| } |
| if (mImages.size() == 0) { |
| // No data at all. |
| mImages.add(0, data); |
| if (mListener != null) { |
| mListener.onDataLoaded(); |
| } |
| return; |
| } |
| |
| LocalData first = mImages.get(0); |
| if (first.getType() == ImageData.TYPE_CAMERA_PREVIEW) { |
| // Replace the old camera data. |
| mImages.set(0, data); |
| if (mListener != null) { |
| mListener.onDataUpdated(new UpdateReporter() { |
| @Override |
| public boolean isDataRemoved(int id) { |
| return false; |
| } |
| |
| @Override |
| public boolean isDataUpdated(int id) { |
| if (id == 0) { |
| return true; |
| } |
| return false; |
| } |
| }); |
| } |
| } else { |
| // Add a new camera data. |
| mImages.add(0, data); |
| if (mListener != null) { |
| mListener.onDataLoaded(); |
| } |
| } |
| } |
| |
| private class QueryTask extends AsyncTask<ContentResolver, Void, List<LocalData>> { |
| @Override |
| protected List<LocalData> doInBackground(ContentResolver... resolver) { |
| List<LocalData> l = new ArrayList<LocalData>(); |
| // Photos |
| Cursor c = resolver[0].query( |
| LocalData.Photo.CONTENT_URI, |
| LocalData.Photo.QUERY_PROJECTION, |
| MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH, |
| LocalData.Photo.QUERY_ORDER); |
| if (c != null && c.moveToFirst()) { |
| // build up the list. |
| while (true) { |
| LocalData data = LocalData.Photo.buildFromCursor(c); |
| if (data != null) { |
| l.add(data); |
| } else { |
| Log.e(TAG, "Error loading data:" |
| + c.getString(LocalData.Photo.COL_DATA)); |
| } |
| if (c.isLast()) { |
| break; |
| } |
| c.moveToNext(); |
| } |
| } |
| if (c != null) { |
| c.close(); |
| } |
| |
| c = resolver[0].query( |
| LocalData.Video.CONTENT_URI, |
| LocalData.Video.QUERY_PROJECTION, |
| MediaStore.Video.Media.DATA + " like ? ", CAMERA_PATH, |
| LocalData.Video.QUERY_ORDER); |
| if (c != null && c.moveToFirst()) { |
| // build up the list. |
| c.moveToFirst(); |
| while (true) { |
| LocalData data = LocalData.Video.buildFromCursor(c); |
| if (data != null) { |
| l.add(data); |
| Log.v(TAG, "video data added:" + data); |
| } else { |
| Log.e(TAG, "Error loading data:" |
| + c.getString(LocalData.Video.COL_DATA)); |
| } |
| if (!c.isLast()) { |
| c.moveToNext(); |
| } else { |
| break; |
| } |
| } |
| } |
| if (c != null) { |
| c.close(); |
| } |
| |
| if (l.size() == 0) return null; |
| |
| Collections.sort(l, new LocalData.NewestFirstComparator()); |
| return l; |
| } |
| |
| @Override |
| protected void onPostExecute(List<LocalData> l) { |
| replaceData(l); |
| } |
| } |
| |
| private class CameraPreviewData implements LocalData { |
| private int width; |
| private int height; |
| |
| CameraPreviewData(int w, int h) { |
| width = w; |
| height = h; |
| } |
| |
| @Override |
| public long getDateTaken() { |
| // This value is used for sorting. |
| return -1; |
| } |
| |
| @Override |
| public long getDateModified() { |
| // This value might be used for sorting. |
| return -1; |
| } |
| |
| @Override |
| public String getTitle() { |
| return ""; |
| } |
| |
| @Override |
| public int getWidth() { |
| return width; |
| } |
| |
| @Override |
| public int getHeight() { |
| return height; |
| } |
| |
| @Override |
| public int getType() { |
| return ImageData.TYPE_CAMERA_PREVIEW; |
| } |
| |
| @Override |
| public boolean isActionSupported(int action) { |
| return false; |
| } |
| |
| @Override |
| public View getView(Context c, int width, int height, Drawable placeHolder) { |
| return mCameraPreviewView; |
| } |
| |
| @Override |
| public void prepare() { |
| // do nothing. |
| } |
| |
| @Override |
| public void recycle() { |
| // do nothing. |
| } |
| } |
| } |