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 17 package com.cooliris.media; 18 19 import java.io.BufferedInputStream; 20 import java.io.BufferedOutputStream; 21 import java.io.File; 22 import java.io.FileInputStream; 23 import java.io.FileNotFoundException; 24 import java.io.FileOutputStream; 25 import java.io.IOException; 26 import java.io.InputStream; 27 import java.net.URI; 28 import java.net.URISyntaxException; 29 import java.net.URL; 30 import java.net.URLConnection; 31 32 import org.apache.http.HttpEntity; 33 import org.apache.http.HttpResponse; 34 import org.apache.http.client.methods.HttpGet; 35 import org.apache.http.client.methods.HttpUriRequest; 36 import org.apache.http.client.params.HttpClientParams; 37 import org.apache.http.conn.ClientConnectionManager; 38 import org.apache.http.conn.scheme.PlainSocketFactory; 39 import org.apache.http.conn.scheme.Scheme; 40 import org.apache.http.conn.scheme.SchemeRegistry; 41 import org.apache.http.impl.client.DefaultHttpClient; 42 import org.apache.http.impl.conn.*; 43 import org.apache.http.params.BasicHttpParams; 44 import org.apache.http.params.HttpConnectionParams; 45 import org.apache.http.params.HttpParams; 46 import org.apache.http.params.HttpProtocolParams; 47 48 import android.content.ContentResolver; 49 import android.content.Context; 50 import android.graphics.Bitmap; 51 import android.graphics.BitmapFactory; 52 import android.net.Uri; 53 import android.util.Log; 54 55 import com.cooliris.cache.CacheService; 56 57 public class UriTexture extends Texture { 58 public static final int MAX_RESOLUTION = 1024; 59 private static final String TAG = "UriTexture"; 60 protected String mUri; 61 protected long mCacheId; 62 private static final int MAX_RESOLUTION_A = MAX_RESOLUTION; 63 private static final int MAX_RESOLUTION_B = MAX_RESOLUTION; 64 public static final String URI_CACHE = CacheService.getCachePath("hires-image-cache"); 65 private static final String USER_AGENT = "Cooliris-ImageDownload"; 66 private static final int CONNECTION_TIMEOUT = 20000; // ms. 67 public static final HttpParams HTTP_PARAMS; 68 public static final SchemeRegistry SCHEME_REGISTRY; 69 static { 70 // Prepare HTTP parameters. 71 HttpParams params = new BasicHttpParams(); 72 HttpConnectionParams.setStaleCheckingEnabled(params, false); 73 HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT); 74 HttpConnectionParams.setSoTimeout(params, CONNECTION_TIMEOUT); 75 HttpClientParams.setRedirecting(params, true); 76 HttpProtocolParams.setUserAgent(params, USER_AGENT); 77 HTTP_PARAMS = params; 78 79 // Register HTTP protocol. 80 SCHEME_REGISTRY = new SchemeRegistry(); 81 SCHEME_REGISTRY.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); 82 } 83 84 private SingleClientConnManager mConnectionManager; 85 86 static { 87 File uri_cache = new File(URI_CACHE); 88 uri_cache.mkdirs(); 89 } 90 91 public UriTexture(String imageUri) { 92 mUri = imageUri; 93 } 94 95 public void setCacheId(long id) { 96 mCacheId = id; 97 } 98 99 private static int computeSampleSize(InputStream stream, int maxResolutionX, 100 int maxResolutionY) { 101 BitmapFactory.Options options = new BitmapFactory.Options(); 102 options.inJustDecodeBounds = true; 103 BitmapFactory.decodeStream(stream, null, options); 104 int maxNumOfPixels = maxResolutionX * maxResolutionY; 105 int minSideLength = Math.min(maxResolutionX, maxResolutionY) / 2; 106 return Utils.computeSampleSize(options, minSideLength, maxNumOfPixels); 107 } 108 109 public static final Bitmap createFromUri(Context context, String uri, int maxResolutionX, int maxResolutionY, long cacheId, 110 ClientConnectionManager connectionManager) throws IOException, URISyntaxException, OutOfMemoryError { 111 final BitmapFactory.Options options = new BitmapFactory.Options(); 112 options.inScaled = false; 113 options.inPreferredConfig = Bitmap.Config.RGB_565; 114 options.inDither = true; 115 long crc64 = 0; 116 Bitmap bitmap = null; 117 if (uri.startsWith(ContentResolver.SCHEME_CONTENT)) { 118 // We need the filepath for the given content uri 119 crc64 = cacheId; 120 } else { 121 crc64 = Utils.Crc64Long(uri); 122 } 123 bitmap = createFromCache(crc64, maxResolutionX); 124 if (bitmap != null) { 125 return bitmap; 126 } 127 final boolean local = uri.startsWith(ContentResolver.SCHEME_CONTENT) || uri.startsWith("file://"); 128 129 // Get the input stream for computing the sample size. 130 BufferedInputStream bufferedInput = null; 131 if (uri.startsWith(ContentResolver.SCHEME_CONTENT) || 132 uri.startsWith(ContentResolver.SCHEME_FILE)) { 133 // Get the stream from a local file. 134 bufferedInput = new BufferedInputStream(context.getContentResolver() 135 .openInputStream(Uri.parse(uri)), 16384); 136 } else { 137 // Get the stream from a remote URL. 138 bufferedInput = createInputStreamFromRemoteUrl(uri, connectionManager); 139 } 140 141 // Compute the sample size, i.e., not decoding real pixels. 142 if (bufferedInput != null) { 143 options.inSampleSize = computeSampleSize(bufferedInput, maxResolutionX, maxResolutionY); 144 } else { 145 return null; 146 } 147 148 // Get the input stream again for decoding it to a bitmap. 149 bufferedInput = null; 150 if (uri.startsWith(ContentResolver.SCHEME_CONTENT) || 151 uri.startsWith(ContentResolver.SCHEME_FILE)) { 152 // Get the stream from a local file. 153 bufferedInput = new BufferedInputStream(context.getContentResolver() 154 .openInputStream(Uri.parse(uri)), 16384); 155 } else { 156 // Get the stream from a remote URL. 157 bufferedInput = createInputStreamFromRemoteUrl(uri, connectionManager); 158 } 159 160 // Decode bufferedInput to a bitmap. 161 if (bufferedInput != null) { 162 options.inDither = false; 163 options.inJustDecodeBounds = false; 164 Thread timeoutThread = new Thread("BitmapTimeoutThread") { 165 public void run() { 166 try { 167 Thread.sleep(6000); 168 options.requestCancelDecode(); 169 } catch (InterruptedException e) { 170 } 171 } 172 }; 173 timeoutThread.start(); 174 175 bitmap = BitmapFactory.decodeStream(bufferedInput, null, options); 176 } 177 178 if ((options.inSampleSize > 1 || !local) && bitmap != null) { 179 writeToCache(crc64, bitmap, maxResolutionX / options.inSampleSize); 180 } 181 return bitmap; 182 } 183 184 private static final BufferedInputStream createInputStreamFromRemoteUrl( 185 String uri, ClientConnectionManager connectionManager) { 186 InputStream contentInput = null; 187 if (connectionManager == null) { 188 try { 189 URL url = new URI(uri).toURL(); 190 URLConnection conn = url.openConnection(); 191 conn.connect(); 192 contentInput = conn.getInputStream(); 193 } catch (Exception e) { 194 Log.w(TAG, "Request failed: " + uri); 195 e.printStackTrace(); 196 return null; 197 } 198 } else { 199 // We create a cancelable http request from the client 200 final DefaultHttpClient mHttpClient = new DefaultHttpClient(connectionManager, HTTP_PARAMS); 201 HttpUriRequest request = new HttpGet(uri); 202 // Execute the HTTP request. 203 HttpResponse httpResponse = null; 204 try { 205 httpResponse = mHttpClient.execute(request); 206 HttpEntity entity = httpResponse.getEntity(); 207 if (entity != null) { 208 // Wrap the entity input stream in a GZIP decoder if 209 // necessary. 210 contentInput = entity.getContent(); 211 } 212 } catch (Exception e) { 213 Log.w(TAG, "Request failed: " + request.getURI()); 214 return null; 215 } 216 } 217 if (contentInput != null) { 218 return new BufferedInputStream(contentInput, 4096); 219 } else { 220 return null; 221 } 222 } 223 224 @Override 225 protected Bitmap load(RenderView view) { 226 Bitmap bitmap = null; 227 if (mUri == null) 228 return bitmap; 229 try { 230 if (mUri.startsWith("http://")) { 231 if (!isCached(Utils.Crc64Long(mUri), MAX_RESOLUTION_A)) { 232 mConnectionManager = new SingleClientConnManager(HTTP_PARAMS, SCHEME_REGISTRY); 233 } 234 } 235 bitmap = createFromUri(view.getContext(), mUri, MAX_RESOLUTION_A, MAX_RESOLUTION_B, mCacheId, mConnectionManager); 236 } catch (Exception e2) { 237 Log.e(TAG, "Unable to load image from URI " + mUri); 238 e2.printStackTrace(); 239 } 240 return bitmap; 241 } 242 243 public static final String createFilePathFromCrc64(long crc64, int maxResolution) { 244 return URI_CACHE + crc64 + "_" + maxResolution + ".cache"; 245 } 246 247 public static boolean isCached(long crc64, int maxResolution) { 248 String file = null; 249 if (crc64 != 0) { 250 file = createFilePathFromCrc64(crc64, maxResolution); 251 try { 252 new FileInputStream(file); 253 return true; 254 } catch (FileNotFoundException e) { 255 return false; 256 } 257 } 258 return false; 259 } 260 261 public static Bitmap createFromCache(long crc64, int maxResolution) { 262 try { 263 String file = null; 264 Bitmap bitmap = null; 265 final BitmapFactory.Options options = new BitmapFactory.Options(); 266 options.inScaled = false; 267 options.inPreferredConfig = Bitmap.Config.RGB_565; 268 options.inDither = true; 269 if (crc64 != 0) { 270 file = createFilePathFromCrc64(crc64, maxResolution); 271 bitmap = BitmapFactory.decodeFile(file, options); 272 } 273 return bitmap; 274 } catch (Exception e) { 275 return null; 276 } 277 } 278 279 public static String writeHttpDataInDirectory(Context context, String uri, String path) { 280 long crc64 = Utils.Crc64Long(uri); 281 if (!isCached(crc64, 1024)) { 282 Bitmap bitmap; 283 try { 284 bitmap = UriTexture.createFromUri(context, uri, 1024, 1024, crc64, null); 285 } catch (OutOfMemoryError e) { 286 return null; 287 } catch (IOException e) { 288 return null; 289 } catch (URISyntaxException e) { 290 return null; 291 } 292 bitmap.recycle(); 293 } 294 String fileString = createFilePathFromCrc64(crc64, 1024); 295 try { 296 File file = new File(fileString); 297 if (file.exists()) { 298 // We write a copy of this file 299 String newPath = path + (path.endsWith("/") ? "" : "/") + crc64 + ".jpg"; 300 File newFile = new File(newPath); 301 Utils.Copy(file, newFile); 302 return newPath; 303 } 304 return null; 305 } catch (Exception e) { 306 return null; 307 } 308 } 309 310 public static void writeToCache(long crc64, Bitmap bitmap, int maxResolution) { 311 String file = createFilePathFromCrc64(crc64, maxResolution); 312 if (bitmap != null && file != null && crc64 != 0) { 313 try { 314 File fileC = new File(file); 315 fileC.createNewFile(); 316 final FileOutputStream fos = new FileOutputStream(fileC); 317 final BufferedOutputStream bos = new BufferedOutputStream(fos, 16384); 318 bitmap.compress(Bitmap.CompressFormat.JPEG, 80, bos); 319 bos.flush(); 320 bos.close(); 321 fos.close(); 322 } catch (Exception e) { 323 324 } 325 } 326 } 327 328 public static void invalidateCache(long crc64, int maxResolution) { 329 String file = createFilePathFromCrc64(crc64, maxResolution); 330 if (file != null && crc64 != 0) { 331 try { 332 File fileC = new File(file); 333 fileC.delete(); 334 } catch (Exception e) { 335 336 } 337 } 338 339 } 340 341 @Override 342 public void finalize() { 343 if (mConnectionManager != null) { 344 mConnectionManager.shutdown(); 345 } 346 } 347 } 348