PhotoSource.java revision d9b659aa5dfa4a3af96582ae49ba9ae145854a84
183fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren/* 283fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren * Copyright (C) 2012 The Android Open Source Project 383fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren * 483fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren * Licensed under the Apache License, Version 2.0 (the "License"); 583fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren * you may not use this file except in compliance with the License. 683fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren * You may obtain a copy of the License at 783fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren * 883fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren * http://www.apache.org/licenses/LICENSE-2.0 983fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren * 1083fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren * Unless required by applicable law or agreed to in writing, software 1183fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren * distributed under the License is distributed on an "AS IS" BASIS, 1283fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1383fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren * See the License for the specific language governing permissions and 1483fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren * limitations under the License. 1583fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren */ 1683fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wrenpackage com.android.dreams.phototable; 1783fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren 1883fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wrenimport android.content.ContentResolver; 1983fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wrenimport android.content.Context; 20d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.content.SharedPreferences; 2183fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wrenimport android.content.res.Resources; 2283fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wrenimport android.database.Cursor; 2383fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wrenimport android.graphics.Bitmap; 2483fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wrenimport android.graphics.BitmapFactory; 2583fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wrenimport android.graphics.Matrix; 2683fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wrenimport android.net.Uri; 2783fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wrenimport android.provider.MediaStore; 2883fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wrenimport android.util.Log; 2983fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren 3083fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wrenimport java.io.FileNotFoundException; 3183fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wrenimport java.io.InputStream; 3283fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wrenimport java.io.IOException; 3383fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wrenimport java.io.BufferedInputStream; 3483fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wrenimport java.util.Collection; 3583fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wrenimport java.util.Collections; 3683fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wrenimport java.util.LinkedList; 3783fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wrenimport java.util.Random; 3883fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren 3983fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren/** 4083fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren * Picks a random image from a source of photos. 4183fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren */ 4283fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wrenpublic abstract class PhotoSource { 4383fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren private static final String TAG = "PhotoTable.PhotoSource"; 44b7fe7200dcc6efc90aa5441bb8366d3205452c3eChris Wren private static final boolean DEBUG = false; 4583fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren 4683fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren // This should be large enough for BitmapFactory to decode the header so 4783fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren // that we can mark and reset the input stream to avoid duplicate network i/o 4883fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren private static final int BUFFER_SIZE = 128 * 1024; 4983fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren 50d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren public class ImageData { 5183fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren public String id; 5283fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren public String url; 5383fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren public int orientation; 54d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren 55d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren InputStream getStream() { 56d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren return PhotoSource.this.getStream(this); 57d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren } 58d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren } 59d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren 60e38c0c80e3e9b3b835e5c2e014ccf23e29663396Chris Wren public class AlbumData { 61d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren public String id; 62d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren public String title; 63d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren public String thumbnailUrl; 64e38c0c80e3e9b3b835e5c2e014ccf23e29663396Chris Wren public String account; 65d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren public long updated; 66d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren 67e38c0c80e3e9b3b835e5c2e014ccf23e29663396Chris Wren public String getType() { 68e38c0c80e3e9b3b835e5c2e014ccf23e29663396Chris Wren String type = PhotoSource.this.getClass().getName(); 69e38c0c80e3e9b3b835e5c2e014ccf23e29663396Chris Wren log(TAG, "type is " + type); 70e38c0c80e3e9b3b835e5c2e014ccf23e29663396Chris Wren return type; 71d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren } 7283fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren } 7383fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren 7483fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren private final LinkedList<ImageData> mImageQueue; 7583fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren private final int mMaxQueueSize; 765b4b44688dac0053be77b282b7501bd291efb0d3Chris Wren private final float mMaxCropRatio; 77b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren private final PhotoSource mFallbackSource; 7883fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren 79d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren protected final Context mContext; 8083fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren protected final Resources mResources; 8183fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren protected final Random mRNG; 82d9b659aa5dfa4a3af96582ae49ba9ae145854a84Chris Wren protected final AlbumSettings mSettings; 83d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren protected final ContentResolver mResolver; 84d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren 85d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren protected String mSourceName; 8683fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren 87d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren public PhotoSource(Context context, SharedPreferences settings) { 88b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren this(context, settings, new StockSource(context, settings)); 89b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren } 90b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren 91b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren public PhotoSource(Context context, SharedPreferences settings, PhotoSource fallbackSource) { 9283fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren mSourceName = TAG; 9383fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren mContext = context; 94d9b659aa5dfa4a3af96582ae49ba9ae145854a84Chris Wren mSettings = AlbumSettings.getAlbumSettings(settings); 9583fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren mResolver = mContext.getContentResolver(); 9683fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren mResources = context.getResources(); 9783fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren mImageQueue = new LinkedList<ImageData>(); 9883fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren mMaxQueueSize = mResources.getInteger(R.integer.image_queue_size); 995b4b44688dac0053be77b282b7501bd291efb0d3Chris Wren mMaxCropRatio = mResources.getInteger(R.integer.max_crop_ratio) / 1000000f; 10083fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren mRNG = new Random(); 101b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren mFallbackSource = fallbackSource; 10283fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren } 10383fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren 10483fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren protected void fillQueue() { 10583fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren log(TAG, "filling queue"); 10683fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren mImageQueue.addAll(findImages(mMaxQueueSize - mImageQueue.size())); 10783fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren Collections.shuffle(mImageQueue); 10883fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren log(TAG, "queue contains: " + mImageQueue.size() + " items."); 10983fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren } 11083fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren 11183fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren public Bitmap next(BitmapFactory.Options options, int longSide, int shortSide) { 11283fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren log(TAG, "decoding a picasa resource to " + longSide + ", " + shortSide); 11383fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren Bitmap image = null; 11483fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren 11583fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren if (mImageQueue.isEmpty()) { 11683fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren fillQueue(); 11783fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren } 11883fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren 11983fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren if (!mImageQueue.isEmpty()) { 120b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren image = load(mImageQueue.poll(), options, longSide, shortSide); 121b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren } 12283fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren 123b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren if (image == null && mFallbackSource != null) { 124b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren image = load((ImageData) mFallbackSource.findImages(1).toArray()[0], 125b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren options, longSide, shortSide); 126b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren } 12783fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren 128b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren return image; 129b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren } 13083fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren 131b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren public Bitmap load(ImageData data, BitmapFactory.Options options, int longSide, int shortSide) { 132b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren log(TAG, "decoding photo resource to " + longSide + ", " + shortSide); 133b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren InputStream is = data.getStream(); 134b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren 135b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren Bitmap image = null; 136b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren try { 137b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren BufferedInputStream bis = new BufferedInputStream(is); 138b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren bis.mark(BUFFER_SIZE); 139b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren 140b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren options.inJustDecodeBounds = true; 141b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren options.inSampleSize = 1; 142b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren image = BitmapFactory.decodeStream(new BufferedInputStream(bis), null, options); 143b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren int rawLongSide = Math.max(options.outWidth, options.outHeight); 144b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren int rawShortSide = Math.min(options.outWidth, options.outHeight); 145b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren log(TAG, "I see bounds of " + rawLongSide + ", " + rawShortSide); 146b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren 147b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren if (rawLongSide != -1 && rawShortSide != -1) { 148b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren float insideRatio = Math.max((float) longSide / (float) rawLongSide, 149b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren (float) shortSide / (float) rawShortSide); 150b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren float outsideRatio = Math.max((float) longSide / (float) rawLongSide, 151b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren (float) shortSide / (float) rawShortSide); 152b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren float ratio = (outsideRatio / insideRatio < mMaxCropRatio ? 153b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren outsideRatio : insideRatio); 154b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren 155b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren while (ratio < 0.5) { 156b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren options.inSampleSize *= 2; 157b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren ratio *= 2; 15883fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren } 159b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren 160b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren log(TAG, "decoding with inSampleSize " + options.inSampleSize); 161b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren bis.reset(); 162b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren options.inJustDecodeBounds = false; 163b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren image = BitmapFactory.decodeStream(bis, null, options); 164b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren rawLongSide = Math.max(options.outWidth, options.outHeight); 165b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren rawShortSide = Math.max(options.outWidth, options.outHeight); 166b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren ratio = Math.max((float) longSide / (float) rawLongSide, 167b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren (float) shortSide / (float) rawShortSide); 168b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren 169b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren if (Math.abs(ratio - 1.0f) > 0.001) { 170b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren log(TAG, "still too big, scaling down by " + ratio); 171b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren options.outWidth = (int) (ratio * options.outWidth); 172b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren options.outHeight = (int) (ratio * options.outHeight); 173b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren 174b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren image = Bitmap.createScaledBitmap(image, 175b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren options.outWidth, options.outHeight, 176b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren true); 177b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren } 178b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren 179b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren if (data.orientation != 0) { 180b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren log(TAG, "rotated by " + data.orientation + ": fixing"); 181b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren if (data.orientation == 90 || data.orientation == 270) { 182b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren int tmp = options.outWidth; 183b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren options.outWidth = options.outHeight; 184b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren options.outHeight = tmp; 18583fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren } 186b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren Matrix matrix = new Matrix(); 187b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren matrix.setRotate(data.orientation, 188b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren (float) image.getWidth() / 2, 189b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren (float) image.getHeight() / 2); 190b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren image = Bitmap.createBitmap(image, 0, 0, 191b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren options.outHeight, options.outWidth, 192b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren matrix, true); 193b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren } 194b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren 195b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren log(TAG, "returning bitmap " + image.getWidth() + ", " + image.getHeight()); 196b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren } else { 197b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren log(TAG, "decoding failed with no error: " + options.mCancel); 198b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren } 199b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren } catch (FileNotFoundException fnf) { 200b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren log(TAG, "file not found: " + fnf); 201b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren } catch (IOException ioe) { 202b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren log(TAG, "i/o exception: " + ioe); 203b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren } finally { 204b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren try { 205b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren if (is != null) { 206b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren is.close(); 20783fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren } 208b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren } catch (Throwable t) { 209b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren log(TAG, "close fail: " + t.toString()); 21083fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren } 21183fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren } 21283fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren 21383fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren return image; 21483fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren } 21583fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren 21683fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren public void setSeed(long seed) { 21783fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren mRNG.setSeed(seed); 21883fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren } 21983fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren 220b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren protected static void log(String tag, String message) { 22183fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren if (DEBUG) { 22283fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren Log.i(tag, message); 22383fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren } 22483fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren } 22583fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren 22683fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren protected abstract InputStream getStream(ImageData data); 22783fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren protected abstract Collection<ImageData> findImages(int howMany); 228d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren public abstract Collection<AlbumData> findAlbums(); 22983fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren} 230