• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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 package com.android.dreams.phototable;
17 
18 import android.content.ContentResolver;
19 import android.content.Context;
20 import android.content.SharedPreferences;
21 import android.content.res.Resources;
22 import android.database.Cursor;
23 import android.graphics.Bitmap;
24 import android.graphics.BitmapFactory;
25 import android.graphics.Matrix;
26 import android.net.Uri;
27 import android.provider.MediaStore;
28 import android.util.Log;
29 
30 import java.io.FileNotFoundException;
31 import java.io.InputStream;
32 import java.io.IOException;
33 import java.io.BufferedInputStream;
34 import java.util.Collection;
35 import java.util.Collections;
36 import java.util.LinkedList;
37 import java.util.Random;
38 
39 /**
40  * Picks a random image from a source of photos.
41  */
42 public abstract class PhotoSource {
43     private static final String TAG = "PhotoTable.PhotoSource";
44     private static final boolean DEBUG = false;
45 
46     // This should be large enough for BitmapFactory to decode the header so
47     // that we can mark and reset the input stream to avoid duplicate network i/o
48     private static final int BUFFER_SIZE = 128 * 1024;
49 
50     public class ImageData {
51         public String id;
52         public String url;
53         public int orientation;
54 
getStream(int longSide)55         InputStream getStream(int longSide) {
56             return PhotoSource.this.getStream(this, longSide);
57         }
58     }
59 
60     public class AlbumData {
61         public String id;
62         public String title;
63         public String thumbnailUrl;
64         public String account;
65         public long updated;
66 
getType()67         public String getType() {
68             String type = PhotoSource.this.getClass().getName();
69             log(TAG, "type is " + type);
70             return type;
71         }
72     }
73 
74     private final LinkedList<ImageData> mImageQueue;
75     private final int mMaxQueueSize;
76     private final float mMaxCropRatio;
77     private final int mBadImageSkipLimit;
78     private final PhotoSource mFallbackSource;
79 
80     protected final Context mContext;
81     protected final Resources mResources;
82     protected final Random mRNG;
83     protected final AlbumSettings mSettings;
84     protected final ContentResolver mResolver;
85 
86     protected String mSourceName;
87 
PhotoSource(Context context, SharedPreferences settings)88     public PhotoSource(Context context, SharedPreferences settings) {
89         this(context, settings, new StockSource(context, settings));
90     }
91 
PhotoSource(Context context, SharedPreferences settings, PhotoSource fallbackSource)92     public PhotoSource(Context context, SharedPreferences settings, PhotoSource fallbackSource) {
93         mSourceName = TAG;
94         mContext = context;
95         mSettings = AlbumSettings.getAlbumSettings(settings);
96         mResolver = mContext.getContentResolver();
97         mResources = context.getResources();
98         mImageQueue = new LinkedList<ImageData>();
99         mMaxQueueSize = mResources.getInteger(R.integer.image_queue_size);
100         mMaxCropRatio = mResources.getInteger(R.integer.max_crop_ratio) / 1000000f;
101         mBadImageSkipLimit = mResources.getInteger(R.integer.bad_image_skip_limit);
102         mRNG = new Random();
103         mFallbackSource = fallbackSource;
104     }
105 
fillQueue()106     protected void fillQueue() {
107         log(TAG, "filling queue");
108         mImageQueue.addAll(findImages(mMaxQueueSize - mImageQueue.size()));
109         Collections.shuffle(mImageQueue);
110         log(TAG, "queue contains: " + mImageQueue.size() + " items.");
111     }
112 
next(BitmapFactory.Options options, int longSide, int shortSide)113     public Bitmap next(BitmapFactory.Options options, int longSide, int shortSide) {
114         log(TAG, "decoding a picasa resource to " +  longSide + ", " + shortSide);
115         Bitmap image = null;
116         ImageData imageData = null;
117         int tries = 0;
118 
119         while (image == null && tries < mBadImageSkipLimit) {
120             synchronized(mImageQueue) {
121                 if (mImageQueue.isEmpty()) {
122                     fillQueue();
123                 }
124 
125                 imageData = mImageQueue.poll();
126             }
127             if (imageData != null) {
128                 image = load(imageData, options, longSide, shortSide);
129                 imageData = null;
130             }
131 
132             tries++;
133         }
134 
135         if (image == null && mFallbackSource != null) {
136             image = load((ImageData) mFallbackSource.findImages(1).toArray()[0],
137                     options, longSide, shortSide);
138         }
139 
140         return image;
141     }
142 
load(ImageData data, BitmapFactory.Options options, int longSide, int shortSide)143     public Bitmap load(ImageData data, BitmapFactory.Options options, int longSide, int shortSide) {
144         log(TAG, "decoding photo resource to " +  longSide + ", " + shortSide);
145         InputStream is = data.getStream(longSide);
146 
147         Bitmap image = null;
148         try {
149             BufferedInputStream bis = new BufferedInputStream(is);
150             bis.mark(BUFFER_SIZE);
151 
152             options.inJustDecodeBounds = true;
153             options.inSampleSize = 1;
154             image = BitmapFactory.decodeStream(new BufferedInputStream(bis), null, options);
155             int rawLongSide = Math.max(options.outWidth, options.outHeight);
156             int rawShortSide = Math.min(options.outWidth, options.outHeight);
157             log(TAG, "I see bounds of " +  rawLongSide + ", " + rawShortSide);
158 
159             if (rawLongSide != -1 && rawShortSide != -1) {
160                 float insideRatio = Math.max((float) longSide / (float) rawLongSide,
161                                              (float) shortSide / (float) rawShortSide);
162                 float outsideRatio = Math.max((float) longSide / (float) rawLongSide,
163                                               (float) shortSide / (float) rawShortSide);
164                 float ratio = (outsideRatio / insideRatio < mMaxCropRatio ?
165                                outsideRatio : insideRatio);
166 
167                 while (ratio < 0.5) {
168                     options.inSampleSize *= 2;
169                     ratio *= 2;
170                 }
171 
172                 log(TAG, "decoding with inSampleSize " +  options.inSampleSize);
173                 bis.reset();
174                 options.inJustDecodeBounds = false;
175                 image = BitmapFactory.decodeStream(bis, null, options);
176                 rawLongSide = Math.max(options.outWidth, options.outHeight);
177                 rawShortSide = Math.max(options.outWidth, options.outHeight);
178                 if (image != null && rawLongSide != -1 && rawShortSide != -1) {
179                     ratio = Math.max((float) longSide / (float) rawLongSide,
180                             (float) shortSide / (float) rawShortSide);
181 
182                     if (Math.abs(ratio - 1.0f) > 0.001) {
183                         log(TAG, "still too big, scaling down by " + ratio);
184                         options.outWidth = (int) (ratio * options.outWidth);
185                         options.outHeight = (int) (ratio * options.outHeight);
186 
187                         image = Bitmap.createScaledBitmap(image,
188                                 options.outWidth, options.outHeight,
189                                 true);
190                     }
191 
192                     if (data.orientation != 0) {
193                         log(TAG, "rotated by " + data.orientation + ": fixing");
194                         Matrix matrix = new Matrix();
195                         matrix.setRotate(data.orientation,
196                                 (float) Math.floor(image.getWidth() / 2f),
197                                 (float) Math.floor(image.getHeight() / 2f));
198                         image = Bitmap.createBitmap(image, 0, 0,
199                                                     options.outWidth, options.outHeight,
200                                                     matrix, true);
201                         if (data.orientation == 90 || data.orientation == 270) {
202                             int tmp = options.outWidth;
203                             options.outWidth = options.outHeight;
204                             options.outHeight = tmp;
205                         }
206                     }
207 
208                     log(TAG, "returning bitmap " + image.getWidth() + ", " + image.getHeight());
209                 } else {
210                     image = null;
211                 }
212             } else {
213                 image = null;
214             }
215             if (image == null) {
216                 log(TAG, "Stream decoding failed with no error" +
217                         (options.mCancel ? " due to cancelation." : "."));
218             }
219         } catch (OutOfMemoryError ome) {
220             log(TAG, "OUT OF MEMORY: " + ome);
221             image = null;
222         } catch (FileNotFoundException fnf) {
223             log(TAG, "file not found: " + fnf);
224             image = null;
225         } catch (IOException ioe) {
226             log(TAG, "i/o exception: " + ioe);
227             image = null;
228         } finally {
229             try {
230                 if (is != null) {
231                     is.close();
232                 }
233             } catch (Throwable t) {
234                 log(TAG, "close fail: " + t.toString());
235             }
236         }
237 
238         return image;
239     }
240 
setSeed(long seed)241     public void setSeed(long seed) {
242         mRNG.setSeed(seed);
243     }
244 
log(String tag, String message)245     protected static void log(String tag, String message) {
246         if (DEBUG) {
247             Log.i(tag, message);
248         }
249     }
250 
getStream(ImageData data, int longSide)251     protected abstract InputStream getStream(ImageData data, int longSide);
findImages(int howMany)252     protected abstract Collection<ImageData> findImages(int howMany);
findAlbums()253     public abstract Collection<AlbumData> findAlbums();
254 }
255