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