blob: cc42db72977cc3c9ba8ca8147d8008ea40fb0058 [file] [log] [blame]
/*
* Copyright (C) 2009 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.gallery;
import com.android.camera.ImageManager;
import com.android.camera.Util;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A collection of <code>BaseImage</code>s.
*/
public abstract class BaseImageList implements IImageList {
private static final String TAG = "BaseImageList";
private static final int CACHE_CAPACITY = 512;
private final LruCache<Integer, BaseImage> mCache =
new LruCache<Integer, BaseImage>(CACHE_CAPACITY);
protected ContentResolver mContentResolver;
protected int mSort;
protected Uri mBaseUri;
protected Cursor mCursor;
protected String mBucketId;
protected boolean mCursorDeactivated = false;
public BaseImageList(ContentResolver resolver, Uri uri, int sort,
String bucketId) {
mSort = sort;
mBaseUri = uri;
mBucketId = bucketId;
mContentResolver = resolver;
mCursor = createCursor();
if (mCursor == null) {
Log.w(TAG, "createCursor returns null.");
}
// TODO: We need to clear the cache because we may "reopen" the image
// list. After we implement the image list state, we can remove this
// kind of usage.
mCache.clear();
}
public void close() {
try {
invalidateCursor();
} catch (IllegalStateException e) {
// IllegalStateException may be thrown if the cursor is stale.
Log.e(TAG, "Caught exception while deactivating cursor.", e);
}
mContentResolver = null;
if (mCursor != null) {
mCursor.close();
mCursor = null;
}
}
// TODO: Change public to protected
public Uri contentUri(long id) {
// TODO: avoid using exception for most cases
try {
// does our uri already have an id (single image query)?
// if so just return it
long existingId = ContentUris.parseId(mBaseUri);
if (existingId != id) Log.e(TAG, "id mismatch");
return mBaseUri;
} catch (NumberFormatException ex) {
// otherwise tack on the id
return ContentUris.withAppendedId(mBaseUri, id);
}
}
public int getCount() {
Cursor cursor = getCursor();
if (cursor == null) return 0;
synchronized (this) {
return cursor.getCount();
}
}
public boolean isEmpty() {
return getCount() == 0;
}
private Cursor getCursor() {
synchronized (this) {
if (mCursor == null) return null;
if (mCursorDeactivated) {
mCursor.requery();
mCursorDeactivated = false;
}
return mCursor;
}
}
public IImage getImageAt(int i) {
BaseImage result = mCache.get(i);
if (result == null) {
Cursor cursor = getCursor();
if (cursor == null) return null;
synchronized (this) {
result = cursor.moveToPosition(i)
? loadImageFromCursor(cursor)
: null;
mCache.put(i, result);
}
}
return result;
}
public boolean removeImage(IImage image) {
// TODO: need to delete the thumbnails as well
if (mContentResolver.delete(image.fullSizeImageUri(), null, null) > 0) {
((BaseImage) image).onRemove();
invalidateCursor();
invalidateCache();
return true;
} else {
return false;
}
}
public boolean removeImageAt(int i) {
// TODO: need to delete the thumbnails as well
return removeImage(getImageAt(i));
}
protected abstract Cursor createCursor();
protected abstract BaseImage loadImageFromCursor(Cursor cursor);
protected abstract long getImageId(Cursor cursor);
protected void invalidateCursor() {
if (mCursor == null) return;
mCursor.deactivate();
mCursorDeactivated = true;
}
protected void invalidateCache() {
mCache.clear();
}
private static final Pattern sPathWithId = Pattern.compile("(.*)/\\d+");
private static String getPathWithoutId(Uri uri) {
String path = uri.getPath();
Matcher matcher = sPathWithId.matcher(path);
return matcher.matches() ? matcher.group(1) : path;
}
private boolean isChildImageUri(Uri uri) {
// Sometimes, the URI of an image contains a query string with key
// "bucketId" inorder to restore the image list. However, the query
// string is not part of the mBaseUri. So, we check only other parts
// of the two Uri to see if they are the same.
Uri base = mBaseUri;
return Util.equals(base.getScheme(), uri.getScheme())
&& Util.equals(base.getHost(), uri.getHost())
&& Util.equals(base.getAuthority(), uri.getAuthority())
&& Util.equals(base.getPath(), getPathWithoutId(uri));
}
public IImage getImageForUri(Uri uri) {
if (!isChildImageUri(uri)) return null;
// Find the id of the input URI.
long matchId;
try {
matchId = ContentUris.parseId(uri);
} catch (NumberFormatException ex) {
Log.i(TAG, "fail to get id in: " + uri, ex);
return null;
}
// TODO: design a better method to get URI of specified ID
Cursor cursor = getCursor();
if (cursor == null) return null;
synchronized (this) {
cursor.moveToPosition(-1); // before first
for (int i = 0; cursor.moveToNext(); ++i) {
if (getImageId(cursor) == matchId) {
BaseImage image = mCache.get(i);
if (image == null) {
image = loadImageFromCursor(cursor);
mCache.put(i, image);
}
return image;
}
}
return null;
}
}
public int getImageIndex(IImage image) {
return ((BaseImage) image).mIndex;
}
// This provides a default sorting order string for subclasses.
// The list is first sorted by date, then by id. The order can be ascending
// or descending, depending on the mSort variable.
// The date is obtained from the "datetaken" column. But if it is null,
// the "date_modified" column is used instead.
protected String sortOrder() {
String ascending =
(mSort == ImageManager.SORT_ASCENDING)
? " ASC"
: " DESC";
// Use DATE_TAKEN if it's non-null, otherwise use DATE_MODIFIED.
// DATE_TAKEN is in milliseconds, but DATE_MODIFIED is in seconds.
String dateExpr =
"case ifnull(datetaken,0)" +
" when 0 then date_modified*1000" +
" else datetaken" +
" end";
// Add id to the end so that we don't ever get random sorting
// which could happen, I suppose, if the date values are the same.
return dateExpr + ascending + ", _id" + ascending;
}
}