BaseImageList.java revision 666ea1b28a76aeba74744148b15099254d918671
1/* 2 * Copyright (C) 2009 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.gallery; 18 19import com.android.camera.ImageManager; 20import com.android.camera.Util; 21 22import android.content.ContentResolver; 23import android.content.ContentUris; 24import android.database.Cursor; 25import android.net.Uri; 26import android.util.Log; 27 28import java.util.regex.Matcher; 29import java.util.regex.Pattern; 30 31/** 32 * A collection of <code>BaseImage</code>s. 33 */ 34public abstract class BaseImageList implements IImageList { 35 private static final String TAG = "BaseImageList"; 36 private static final int CACHE_CAPACITY = 512; 37 private final LruCache<Integer, BaseImage> mCache = 38 new LruCache<Integer, BaseImage>(CACHE_CAPACITY); 39 40 protected ContentResolver mContentResolver; 41 protected int mSort; 42 43 protected Uri mBaseUri; 44 protected Cursor mCursor; 45 protected String mBucketId; 46 protected Uri mThumbUri; 47 protected boolean mCursorDeactivated = false; 48 49 public BaseImageList(ContentResolver resolver, Uri uri, int sort, 50 String bucketId) { 51 mSort = sort; 52 mBaseUri = uri; 53 mBucketId = bucketId; 54 mContentResolver = resolver; 55 mCursor = createCursor(); 56 57 if (mCursor == null) { 58 Log.w(TAG, "createCursor returns null."); 59 } 60 61 // TODO: We need to clear the cache because we may "reopen" the image 62 // list. After we implement the image list state, we can remove this 63 // kind of usage. 64 mCache.clear(); 65 } 66 67 public void close() { 68 try { 69 invalidateCursor(); 70 } catch (IllegalStateException e) { 71 // IllegalStateException may be thrown if the cursor is stale. 72 Log.e(TAG, "Caught exception while deactivating cursor.", e); 73 } 74 mContentResolver = null; 75 if (mCursor != null) { 76 mCursor.close(); 77 mCursor = null; 78 } 79 } 80 81 // TODO: Change public to protected 82 public Uri contentUri(long id) { 83 // TODO: avoid using exception for most cases 84 try { 85 // does our uri already have an id (single image query)? 86 // if so just return it 87 long existingId = ContentUris.parseId(mBaseUri); 88 if (existingId != id) Log.e(TAG, "id mismatch"); 89 return mBaseUri; 90 } catch (NumberFormatException ex) { 91 // otherwise tack on the id 92 return ContentUris.withAppendedId(mBaseUri, id); 93 } 94 } 95 96 public int getCount() { 97 Cursor cursor = getCursor(); 98 if (cursor == null) return 0; 99 synchronized (this) { 100 return cursor.getCount(); 101 } 102 } 103 104 public boolean isEmpty() { 105 return getCount() == 0; 106 } 107 108 private Cursor getCursor() { 109 synchronized (this) { 110 if (mCursor == null) return null; 111 if (mCursorDeactivated) { 112 mCursor.requery(); 113 mCursorDeactivated = false; 114 } 115 return mCursor; 116 } 117 } 118 119 public IImage getImageAt(int i) { 120 BaseImage result = mCache.get(i); 121 if (result == null) { 122 Cursor cursor = getCursor(); 123 if (cursor == null) return null; 124 synchronized (this) { 125 result = cursor.moveToPosition(i) 126 ? loadImageFromCursor(cursor) 127 : null; 128 mCache.put(i, result); 129 } 130 } 131 return result; 132 } 133 134 public boolean removeImage(IImage image) { 135 // TODO: need to delete the thumbnails as well 136 if (mContentResolver.delete(image.fullSizeImageUri(), null, null) > 0) { 137 ((BaseImage) image).onRemove(); 138 invalidateCursor(); 139 invalidateCache(); 140 return true; 141 } else { 142 return false; 143 } 144 } 145 146 public boolean removeImageAt(int i) { 147 // TODO: need to delete the thumbnails as well 148 return removeImage(getImageAt(i)); 149 } 150 151 protected abstract Cursor createCursor(); 152 153 protected abstract BaseImage loadImageFromCursor(Cursor cursor); 154 155 protected abstract long getImageId(Cursor cursor); 156 157 protected void invalidateCursor() { 158 if (mCursor == null) return; 159 mCursor.deactivate(); 160 mCursorDeactivated = true; 161 } 162 163 protected void invalidateCache() { 164 mCache.clear(); 165 } 166 167 private static final Pattern sPathWithId = Pattern.compile("(.*)/\\d+"); 168 169 private static String getPathWithoutId(Uri uri) { 170 String path = uri.getPath(); 171 Matcher matcher = sPathWithId.matcher(path); 172 return matcher.matches() ? matcher.group(1) : path; 173 } 174 175 private boolean isChildImageUri(Uri uri) { 176 // Sometimes, the URI of an image contains a query string with key 177 // "bucketId" inorder to restore the image list. However, the query 178 // string is not part of the mBaseUri. So, we check only other parts 179 // of the two Uri to see if they are the same. 180 Uri base = mBaseUri; 181 return Util.equals(base.getScheme(), uri.getScheme()) 182 && Util.equals(base.getHost(), uri.getHost()) 183 && Util.equals(base.getAuthority(), uri.getAuthority()) 184 && Util.equals(base.getPath(), getPathWithoutId(uri)); 185 } 186 187 public IImage getImageForUri(Uri uri) { 188 if (!isChildImageUri(uri)) return null; 189 // Find the id of the input URI. 190 long matchId; 191 try { 192 matchId = ContentUris.parseId(uri); 193 } catch (NumberFormatException ex) { 194 Log.i(TAG, "fail to get id in: " + uri, ex); 195 return null; 196 } 197 // TODO: design a better method to get URI of specified ID 198 Cursor cursor = getCursor(); 199 if (cursor == null) return null; 200 synchronized (this) { 201 cursor.moveToPosition(-1); // before first 202 for (int i = 0; cursor.moveToNext(); ++i) { 203 if (getImageId(cursor) == matchId) { 204 BaseImage image = mCache.get(i); 205 if (image == null) { 206 image = loadImageFromCursor(cursor); 207 mCache.put(i, image); 208 } 209 return image; 210 } 211 } 212 return null; 213 } 214 } 215 216 public int getImageIndex(IImage image) { 217 return ((BaseImage) image).mIndex; 218 } 219 220 // This provides a default sorting order string for subclasses. 221 // The list is first sorted by date, then by id. The order can be ascending 222 // or descending, depending on the mSort variable. 223 // The date is obtained from the "datetaken" column. But if it is null, 224 // the "date_modified" column is used instead. 225 protected String sortOrder() { 226 String ascending = 227 (mSort == ImageManager.SORT_ASCENDING) 228 ? " ASC" 229 : " DESC"; 230 231 // Use DATE_TAKEN if it's non-null, otherwise use DATE_MODIFIED. 232 // DATE_TAKEN is in milliseconds, but DATE_MODIFIED is in seconds. 233 String dateExpr = 234 "case ifnull(datetaken,0)" + 235 " when 0 then date_modified*1000" + 236 " else datetaken" + 237 " end"; 238 239 // Add id to the end so that we don't ever get random sorting 240 // which could happen, I suppose, if the date values are the same. 241 return dateExpr + ascending + ", _id" + ascending; 242 } 243} 244