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;
26729f5231bf95ec7946fc4c510d44db303b07614dChris Wrenimport android.net.Uri;
2783fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wrenimport android.util.Log;
2883fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren
2988d80f4471c900628e2cb6eef23029b99af48e09Chris Wrenimport java.io.BufferedInputStream;
3083fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wrenimport java.io.FileNotFoundException;
3183fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wrenimport java.io.IOException;
3288d80f4471c900628e2cb6eef23029b99af48e09Chris Wrenimport java.io.InputStream;
3383fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wrenimport java.util.Collection;
3483fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wrenimport java.util.Collections;
3588d80f4471c900628e2cb6eef23029b99af48e09Chris Wrenimport java.util.HashMap;
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
4823ab8f3366a5eeecd36c5f489a47eb6b8f03bc56Chris Wren    private static final int BUFFER_SIZE = 32 * 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
5588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        protected String albumId;
5688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        protected Cursor cursor;
5788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        protected int position;
58729f5231bf95ec7946fc4c510d44db303b07614dChris Wren        protected Uri uri;
5988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren
60c6bebae5e07c0108294d05e33fbace209d2f9b0dChris Wren        InputStream getStream(int longSide) {
61c6bebae5e07c0108294d05e33fbace209d2f9b0dChris Wren            return PhotoSource.this.getStream(this, longSide);
62d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        }
6388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        ImageData naturalNext() {
6488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            return PhotoSource.this.naturalNext(this);
6588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        }
6688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        ImageData naturalPrevious() {
6788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            return PhotoSource.this.naturalPrevious(this);
6888d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        }
69bcfd4439d730a4d783a02596c8ab444796323aadChris Wren        public void donePaging() {
70bcfd4439d730a4d783a02596c8ab444796323aadChris Wren            PhotoSource.this.donePaging(this);
71bcfd4439d730a4d783a02596c8ab444796323aadChris Wren        }
72d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
73d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
74e38c0c80e3e9b3b835e5c2e014ccf23e29663396Chris Wren    public class AlbumData {
75d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        public String id;
76d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        public String title;
77d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        public String thumbnailUrl;
78e38c0c80e3e9b3b835e5c2e014ccf23e29663396Chris Wren        public String account;
79d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        public long updated;
80d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
81e38c0c80e3e9b3b835e5c2e014ccf23e29663396Chris Wren        public String getType() {
82e38c0c80e3e9b3b835e5c2e014ccf23e29663396Chris Wren            String type = PhotoSource.this.getClass().getName();
83e38c0c80e3e9b3b835e5c2e014ccf23e29663396Chris Wren            log(TAG, "type is " + type);
84e38c0c80e3e9b3b835e5c2e014ccf23e29663396Chris Wren            return type;
85d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        }
8683fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren    }
8783fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren
8883fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren    private final LinkedList<ImageData> mImageQueue;
8983fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren    private final int mMaxQueueSize;
905b4b44688dac0053be77b282b7501bd291efb0d3Chris Wren    private final float mMaxCropRatio;
9158e2a16d9237d2a5674a97062b5d614d8d397845Chris Wren    private final int mBadImageSkipLimit;
92b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren    private final PhotoSource mFallbackSource;
9388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    private final HashMap<Bitmap, ImageData> mImageMap;
9483fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren
95d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    protected final Context mContext;
9683fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren    protected final Resources mResources;
9783fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren    protected final Random mRNG;
98d9b659aa5dfa4a3af96582ae49ba9ae145854a84Chris Wren    protected final AlbumSettings mSettings;
99d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    protected final ContentResolver mResolver;
100d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
101d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    protected String mSourceName;
10283fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren
103d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public PhotoSource(Context context, SharedPreferences settings) {
104b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren        this(context, settings, new StockSource(context, settings));
105b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren    }
106b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren
107b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren    public PhotoSource(Context context, SharedPreferences settings, PhotoSource fallbackSource) {
10883fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren        mSourceName = TAG;
10983fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren        mContext = context;
110d9b659aa5dfa4a3af96582ae49ba9ae145854a84Chris Wren        mSettings = AlbumSettings.getAlbumSettings(settings);
11183fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren        mResolver = mContext.getContentResolver();
11283fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren        mResources = context.getResources();
11383fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren        mImageQueue = new LinkedList<ImageData>();
11483fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren        mMaxQueueSize = mResources.getInteger(R.integer.image_queue_size);
1155b4b44688dac0053be77b282b7501bd291efb0d3Chris Wren        mMaxCropRatio = mResources.getInteger(R.integer.max_crop_ratio) / 1000000f;
11658e2a16d9237d2a5674a97062b5d614d8d397845Chris Wren        mBadImageSkipLimit = mResources.getInteger(R.integer.bad_image_skip_limit);
11788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        mImageMap = new HashMap<Bitmap, ImageData>();
11883fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren        mRNG = new Random();
119b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren        mFallbackSource = fallbackSource;
12083fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren    }
12183fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren
12283fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren    protected void fillQueue() {
12383fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren        log(TAG, "filling queue");
12483fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren        mImageQueue.addAll(findImages(mMaxQueueSize - mImageQueue.size()));
12583fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren        Collections.shuffle(mImageQueue);
12683fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren        log(TAG, "queue contains: " + mImageQueue.size() + " items.");
12783fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren    }
12883fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren
12983fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren    public Bitmap next(BitmapFactory.Options options, int longSide, int shortSide) {
13083fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren        log(TAG, "decoding a picasa resource to " +  longSide + ", " + shortSide);
13183fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren        Bitmap image = null;
1325006d4093ad1455ee98c157a71f57e9ea42b4daeChris Wren        ImageData imageData = null;
13358e2a16d9237d2a5674a97062b5d614d8d397845Chris Wren        int tries = 0;
13483fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren
13558e2a16d9237d2a5674a97062b5d614d8d397845Chris Wren        while (image == null && tries < mBadImageSkipLimit) {
1365006d4093ad1455ee98c157a71f57e9ea42b4daeChris Wren            synchronized(mImageQueue) {
1375006d4093ad1455ee98c157a71f57e9ea42b4daeChris Wren                if (mImageQueue.isEmpty()) {
1385006d4093ad1455ee98c157a71f57e9ea42b4daeChris Wren                    fillQueue();
1395006d4093ad1455ee98c157a71f57e9ea42b4daeChris Wren                }
1405006d4093ad1455ee98c157a71f57e9ea42b4daeChris Wren                imageData = mImageQueue.poll();
1415006d4093ad1455ee98c157a71f57e9ea42b4daeChris Wren            }
14268bc9d986e8eb882dc5f15defea0dab45c440ab6Chris Wren            if (imageData != null) {
1435006d4093ad1455ee98c157a71f57e9ea42b4daeChris Wren                image = load(imageData, options, longSide, shortSide);
14488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                mImageMap.put(image, imageData);
14568bc9d986e8eb882dc5f15defea0dab45c440ab6Chris Wren                imageData = null;
14658e2a16d9237d2a5674a97062b5d614d8d397845Chris Wren            }
14783fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren
14858e2a16d9237d2a5674a97062b5d614d8d397845Chris Wren            tries++;
149b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren        }
15083fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren
151b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren        if (image == null && mFallbackSource != null) {
152b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren            image = load((ImageData) mFallbackSource.findImages(1).toArray()[0],
15358e2a16d9237d2a5674a97062b5d614d8d397845Chris Wren                    options, longSide, shortSide);
154b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren        }
15583fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren
156b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren        return image;
157b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren    }
15883fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren
159b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren    public Bitmap load(ImageData data, BitmapFactory.Options options, int longSide, int shortSide) {
160b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren        log(TAG, "decoding photo resource to " +  longSide + ", " + shortSide);
161c6bebae5e07c0108294d05e33fbace209d2f9b0dChris Wren        InputStream is = data.getStream(longSide);
162b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren
163b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren        Bitmap image = null;
164b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren        try {
165b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren            BufferedInputStream bis = new BufferedInputStream(is);
166b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren            bis.mark(BUFFER_SIZE);
167b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren
168b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren            options.inJustDecodeBounds = true;
169b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren            options.inSampleSize = 1;
170b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren            image = BitmapFactory.decodeStream(new BufferedInputStream(bis), null, options);
171b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren            int rawLongSide = Math.max(options.outWidth, options.outHeight);
172b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren            int rawShortSide = Math.min(options.outWidth, options.outHeight);
173b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren            log(TAG, "I see bounds of " +  rawLongSide + ", " + rawShortSide);
174b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren
175b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren            if (rawLongSide != -1 && rawShortSide != -1) {
176b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren                float insideRatio = Math.max((float) longSide / (float) rawLongSide,
177b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren                                             (float) shortSide / (float) rawShortSide);
178b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren                float outsideRatio = Math.max((float) longSide / (float) rawLongSide,
179b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren                                              (float) shortSide / (float) rawShortSide);
180b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren                float ratio = (outsideRatio / insideRatio < mMaxCropRatio ?
181b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren                               outsideRatio : insideRatio);
182b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren
183b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren                while (ratio < 0.5) {
184b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren                    options.inSampleSize *= 2;
185b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren                    ratio *= 2;
18683fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren                }
187b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren
188b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren                log(TAG, "decoding with inSampleSize " +  options.inSampleSize);
18923ab8f3366a5eeecd36c5f489a47eb6b8f03bc56Chris Wren                try {
19023ab8f3366a5eeecd36c5f489a47eb6b8f03bc56Chris Wren                    bis.reset();
19123ab8f3366a5eeecd36c5f489a47eb6b8f03bc56Chris Wren                } catch (IOException ioe) {
19223ab8f3366a5eeecd36c5f489a47eb6b8f03bc56Chris Wren                    // start over, something went wrong and we read too far into the image.
19323ab8f3366a5eeecd36c5f489a47eb6b8f03bc56Chris Wren                    bis.close();
19423ab8f3366a5eeecd36c5f489a47eb6b8f03bc56Chris Wren                    is = data.getStream(longSide);
19523ab8f3366a5eeecd36c5f489a47eb6b8f03bc56Chris Wren                    bis = new BufferedInputStream(is);
19623ab8f3366a5eeecd36c5f489a47eb6b8f03bc56Chris Wren                    log(TAG, "resetting the stream");
19723ab8f3366a5eeecd36c5f489a47eb6b8f03bc56Chris Wren                }
198b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren                options.inJustDecodeBounds = false;
199b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren                image = BitmapFactory.decodeStream(bis, null, options);
200b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren                rawLongSide = Math.max(options.outWidth, options.outHeight);
201b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren                rawShortSide = Math.max(options.outWidth, options.outHeight);
20289ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren                if (image != null && rawLongSide != -1 && rawShortSide != -1) {
20389ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren                    ratio = Math.max((float) longSide / (float) rawLongSide,
20489ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren                            (float) shortSide / (float) rawShortSide);
20589ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren
20689ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren                    if (Math.abs(ratio - 1.0f) > 0.001) {
20789ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren                        log(TAG, "still too big, scaling down by " + ratio);
20889ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren                        options.outWidth = (int) (ratio * options.outWidth);
20989ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren                        options.outHeight = (int) (ratio * options.outHeight);
21089ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren
21189ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren                        image = Bitmap.createScaledBitmap(image,
21289ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren                                options.outWidth, options.outHeight,
21389ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren                                true);
21489ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren                    }
215b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren
21689ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren                    if (data.orientation != 0) {
21789ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren                        log(TAG, "rotated by " + data.orientation + ": fixing");
2189ab175f5902c66a266261ccf191bc2d213918ecaChris Wren                        Matrix matrix = new Matrix();
2199ab175f5902c66a266261ccf191bc2d213918ecaChris Wren                        matrix.setRotate(data.orientation,
2209ab175f5902c66a266261ccf191bc2d213918ecaChris Wren                                (float) Math.floor(image.getWidth() / 2f),
2219ab175f5902c66a266261ccf191bc2d213918ecaChris Wren                                (float) Math.floor(image.getHeight() / 2f));
2229ab175f5902c66a266261ccf191bc2d213918ecaChris Wren                        image = Bitmap.createBitmap(image, 0, 0,
2239ab175f5902c66a266261ccf191bc2d213918ecaChris Wren                                                    options.outWidth, options.outHeight,
2249ab175f5902c66a266261ccf191bc2d213918ecaChris Wren                                                    matrix, true);
22589ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren                        if (data.orientation == 90 || data.orientation == 270) {
22689ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren                            int tmp = options.outWidth;
22789ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren                            options.outWidth = options.outHeight;
22889ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren                            options.outHeight = tmp;
22989ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren                        }
23083fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren                    }
231b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren
23289ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren                    log(TAG, "returning bitmap " + image.getWidth() + ", " + image.getHeight());
23389ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren                } else {
23489ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren                    image = null;
23589ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren                }
236b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren            } else {
23789ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren                image = null;
23889ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren            }
23989ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren            if (image == null) {
24089ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren                log(TAG, "Stream decoding failed with no error" +
24189ada9791087c8004f300ddd9e8ba58fbc84eaaeChris Wren                        (options.mCancel ? " due to cancelation." : "."));
242b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren            }
2435006d4093ad1455ee98c157a71f57e9ea42b4daeChris Wren        } catch (OutOfMemoryError ome) {
2445006d4093ad1455ee98c157a71f57e9ea42b4daeChris Wren            log(TAG, "OUT OF MEMORY: " + ome);
2455006d4093ad1455ee98c157a71f57e9ea42b4daeChris Wren            image = null;
246b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren        } catch (FileNotFoundException fnf) {
247b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren            log(TAG, "file not found: " + fnf);
2485006d4093ad1455ee98c157a71f57e9ea42b4daeChris Wren            image = null;
249b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren        } catch (IOException ioe) {
250b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren            log(TAG, "i/o exception: " + ioe);
2515006d4093ad1455ee98c157a71f57e9ea42b4daeChris Wren            image = null;
252b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren        } finally {
253b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren            try {
254b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren                if (is != null) {
255b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren                    is.close();
25683fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren                }
257b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren            } catch (Throwable t) {
258b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren                log(TAG, "close fail: " + t.toString());
25983fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren            }
26083fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren        }
26183fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren
26283fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren        return image;
26383fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren    }
26483fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren
26583fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren    public void setSeed(long seed) {
26683fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren        mRNG.setSeed(seed);
26783fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren    }
26883fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren
269b8235acb0fdc33c50e864ec801b93b9750d7600cChris Wren    protected static void log(String tag, String message) {
27083fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren        if (DEBUG) {
27183fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren            Log.i(tag, message);
27283fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren        }
27383fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren    }
27483fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren
275bed703ef28f013639db3cd7a6b8a6e94d61075daChris Wren    protected int pickRandomStart(int total, int max) {
276bed703ef28f013639db3cd7a6b8a6e94d61075daChris Wren        if (max >= total) {
277bed703ef28f013639db3cd7a6b8a6e94d61075daChris Wren            return -1;
278bed703ef28f013639db3cd7a6b8a6e94d61075daChris Wren        } else {
279bed703ef28f013639db3cd7a6b8a6e94d61075daChris Wren            return (mRNG.nextInt() % (total - max)) - 1;
280bed703ef28f013639db3cd7a6b8a6e94d61075daChris Wren        }
281bed703ef28f013639db3cd7a6b8a6e94d61075daChris Wren    }
282bed703ef28f013639db3cd7a6b8a6e94d61075daChris Wren
28388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    public Bitmap naturalNext(Bitmap current, BitmapFactory.Options options,
28488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            int longSide, int shortSide) {
28588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        Bitmap image = null;
28688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        ImageData data = mImageMap.get(current);
28788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        if (data != null) {
28888d80f4471c900628e2cb6eef23029b99af48e09Chris Wren          ImageData next = data.naturalNext();
28988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren          if (next != null) {
29088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            image = load(next, options, longSide, shortSide);
29188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            mImageMap.put(image, next);
29288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren          }
29388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        }
29488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        return image;
29588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    }
29688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren
29788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    public Bitmap naturalPrevious(Bitmap current, BitmapFactory.Options options,
29888d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            int longSide, int shortSide) {
29988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        Bitmap image = null;
30088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        ImageData data = mImageMap.get(current);
30188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        if (current != null) {
30288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren          ImageData prev = data.naturalPrevious();
30388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren          if (prev != null) {
30488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            image = load(prev, options, longSide, shortSide);
30588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            mImageMap.put(image, prev);
30688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren          }
30788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        }
30888d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        return image;
30988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    }
31088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren
311bcfd4439d730a4d783a02596c8ab444796323aadChris Wren    public void donePaging(Bitmap current) {
312bcfd4439d730a4d783a02596c8ab444796323aadChris Wren        ImageData data = mImageMap.get(current);
313bcfd4439d730a4d783a02596c8ab444796323aadChris Wren        if (data != null) {
314bcfd4439d730a4d783a02596c8ab444796323aadChris Wren            data.donePaging();
315bcfd4439d730a4d783a02596c8ab444796323aadChris Wren        }
316bcfd4439d730a4d783a02596c8ab444796323aadChris Wren    }
317bcfd4439d730a4d783a02596c8ab444796323aadChris Wren
318bcfd4439d730a4d783a02596c8ab444796323aadChris Wren    public void recycle(Bitmap trash) {
319bcfd4439d730a4d783a02596c8ab444796323aadChris Wren        if (trash != null) {
320bcfd4439d730a4d783a02596c8ab444796323aadChris Wren            mImageMap.remove(trash);
321bcfd4439d730a4d783a02596c8ab444796323aadChris Wren            trash.recycle();
322bcfd4439d730a4d783a02596c8ab444796323aadChris Wren        }
323bcfd4439d730a4d783a02596c8ab444796323aadChris Wren    }
324bcfd4439d730a4d783a02596c8ab444796323aadChris Wren
325c6bebae5e07c0108294d05e33fbace209d2f9b0dChris Wren    protected abstract InputStream getStream(ImageData data, int longSide);
32683fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren    protected abstract Collection<ImageData> findImages(int howMany);
32788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    protected abstract ImageData naturalNext(ImageData current);
32888d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    protected abstract ImageData naturalPrevious(ImageData current);
329bcfd4439d730a4d783a02596c8ab444796323aadChris Wren    protected abstract void donePaging(ImageData current);
33088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren
331d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public abstract Collection<AlbumData> findAlbums();
33283fee9012b6d5c5940de5b96fe8d98653ba14c0dChris Wren}
333