| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.example.android.bitmapfun.util; |
| |
| import android.content.Context; |
| import android.graphics.Bitmap; |
| import android.net.ConnectivityManager; |
| import android.net.NetworkInfo; |
| import android.os.Build; |
| import android.util.Log; |
| import android.widget.Toast; |
| |
| import com.example.android.bitmapfun.BuildConfig; |
| import com.example.android.bitmapfun.R; |
| |
| import java.io.BufferedInputStream; |
| import java.io.BufferedOutputStream; |
| import java.io.File; |
| import java.io.FileDescriptor; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.net.HttpURLConnection; |
| import java.net.URL; |
| |
| /** |
| * A simple subclass of {@link ImageResizer} that fetches and resizes images fetched from a URL. |
| */ |
| public class ImageFetcher extends ImageResizer { |
| private static final String TAG = "ImageFetcher"; |
| private static final int HTTP_CACHE_SIZE = 10 * 1024 * 1024; // 10MB |
| private static final String HTTP_CACHE_DIR = "http"; |
| private static final int IO_BUFFER_SIZE = 8 * 1024; |
| |
| private DiskLruCache mHttpDiskCache; |
| private File mHttpCacheDir; |
| private boolean mHttpDiskCacheStarting = true; |
| private final Object mHttpDiskCacheLock = new Object(); |
| private static final int DISK_CACHE_INDEX = 0; |
| |
| /** |
| * Initialize providing a target image width and height for the processing images. |
| * |
| * @param context |
| * @param imageWidth |
| * @param imageHeight |
| */ |
| public ImageFetcher(Context context, int imageWidth, int imageHeight) { |
| super(context, imageWidth, imageHeight); |
| init(context); |
| } |
| |
| /** |
| * Initialize providing a single target image size (used for both width and height); |
| * |
| * @param context |
| * @param imageSize |
| */ |
| public ImageFetcher(Context context, int imageSize) { |
| super(context, imageSize); |
| init(context); |
| } |
| |
| private void init(Context context) { |
| checkConnection(context); |
| mHttpCacheDir = ImageCache.getDiskCacheDir(context, HTTP_CACHE_DIR); |
| } |
| |
| @Override |
| protected void initDiskCacheInternal() { |
| super.initDiskCacheInternal(); |
| initHttpDiskCache(); |
| } |
| |
| private void initHttpDiskCache() { |
| if (!mHttpCacheDir.exists()) { |
| mHttpCacheDir.mkdirs(); |
| } |
| synchronized (mHttpDiskCacheLock) { |
| if (ImageCache.getUsableSpace(mHttpCacheDir) > HTTP_CACHE_SIZE) { |
| try { |
| mHttpDiskCache = DiskLruCache.open(mHttpCacheDir, 1, 1, HTTP_CACHE_SIZE); |
| if (BuildConfig.DEBUG) { |
| Log.d(TAG, "HTTP cache initialized"); |
| } |
| } catch (IOException e) { |
| mHttpDiskCache = null; |
| } |
| } |
| mHttpDiskCacheStarting = false; |
| mHttpDiskCacheLock.notifyAll(); |
| } |
| } |
| |
| @Override |
| protected void clearCacheInternal() { |
| super.clearCacheInternal(); |
| synchronized (mHttpDiskCacheLock) { |
| if (mHttpDiskCache != null && !mHttpDiskCache.isClosed()) { |
| try { |
| mHttpDiskCache.delete(); |
| if (BuildConfig.DEBUG) { |
| Log.d(TAG, "HTTP cache cleared"); |
| } |
| } catch (IOException e) { |
| Log.e(TAG, "clearCacheInternal - " + e); |
| } |
| mHttpDiskCache = null; |
| mHttpDiskCacheStarting = true; |
| initHttpDiskCache(); |
| } |
| } |
| } |
| |
| @Override |
| protected void flushCacheInternal() { |
| super.flushCacheInternal(); |
| synchronized (mHttpDiskCacheLock) { |
| if (mHttpDiskCache != null) { |
| try { |
| mHttpDiskCache.flush(); |
| if (BuildConfig.DEBUG) { |
| Log.d(TAG, "HTTP cache flushed"); |
| } |
| } catch (IOException e) { |
| Log.e(TAG, "flush - " + e); |
| } |
| } |
| } |
| } |
| |
| @Override |
| protected void closeCacheInternal() { |
| super.closeCacheInternal(); |
| synchronized (mHttpDiskCacheLock) { |
| if (mHttpDiskCache != null) { |
| try { |
| if (!mHttpDiskCache.isClosed()) { |
| mHttpDiskCache.close(); |
| mHttpDiskCache = null; |
| if (BuildConfig.DEBUG) { |
| Log.d(TAG, "HTTP cache closed"); |
| } |
| } |
| } catch (IOException e) { |
| Log.e(TAG, "closeCacheInternal - " + e); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Simple network connection check. |
| * |
| * @param context |
| */ |
| private void checkConnection(Context context) { |
| final ConnectivityManager cm = |
| (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); |
| final NetworkInfo networkInfo = cm.getActiveNetworkInfo(); |
| if (networkInfo == null || !networkInfo.isConnectedOrConnecting()) { |
| Toast.makeText(context, R.string.no_network_connection_toast, Toast.LENGTH_LONG).show(); |
| Log.e(TAG, "checkConnection - no connection found"); |
| } |
| } |
| |
| /** |
| * The main process method, which will be called by the ImageWorker in the AsyncTask background |
| * thread. |
| * |
| * @param data The data to load the bitmap, in this case, a regular http URL |
| * @return The downloaded and resized bitmap |
| */ |
| private Bitmap processBitmap(String data) { |
| if (BuildConfig.DEBUG) { |
| Log.d(TAG, "processBitmap - " + data); |
| } |
| |
| final String key = ImageCache.hashKeyForDisk(data); |
| FileDescriptor fileDescriptor = null; |
| FileInputStream fileInputStream = null; |
| DiskLruCache.Snapshot snapshot; |
| synchronized (mHttpDiskCacheLock) { |
| // Wait for disk cache to initialize |
| while (mHttpDiskCacheStarting) { |
| try { |
| mHttpDiskCacheLock.wait(); |
| } catch (InterruptedException e) {} |
| } |
| |
| if (mHttpDiskCache != null) { |
| try { |
| snapshot = mHttpDiskCache.get(key); |
| if (snapshot == null) { |
| if (BuildConfig.DEBUG) { |
| Log.d(TAG, "processBitmap, not found in http cache, downloading..."); |
| } |
| DiskLruCache.Editor editor = mHttpDiskCache.edit(key); |
| if (editor != null) { |
| if (downloadUrlToStream(data, |
| editor.newOutputStream(DISK_CACHE_INDEX))) { |
| editor.commit(); |
| } else { |
| editor.abort(); |
| } |
| } |
| snapshot = mHttpDiskCache.get(key); |
| } |
| if (snapshot != null) { |
| fileInputStream = |
| (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX); |
| fileDescriptor = fileInputStream.getFD(); |
| } |
| } catch (IOException e) { |
| Log.e(TAG, "processBitmap - " + e); |
| } catch (IllegalStateException e) { |
| Log.e(TAG, "processBitmap - " + e); |
| } finally { |
| if (fileDescriptor == null && fileInputStream != null) { |
| try { |
| fileInputStream.close(); |
| } catch (IOException e) {} |
| } |
| } |
| } |
| } |
| |
| Bitmap bitmap = null; |
| if (fileDescriptor != null) { |
| bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, mImageWidth, |
| mImageHeight, getImageCache()); |
| } |
| if (fileInputStream != null) { |
| try { |
| fileInputStream.close(); |
| } catch (IOException e) {} |
| } |
| return bitmap; |
| } |
| |
| @Override |
| protected Bitmap processBitmap(Object data) { |
| return processBitmap(String.valueOf(data)); |
| } |
| |
| /** |
| * Download a bitmap from a URL and write the content to an output stream. |
| * |
| * @param urlString The URL to fetch |
| * @return true if successful, false otherwise |
| */ |
| public boolean downloadUrlToStream(String urlString, OutputStream outputStream) { |
| disableConnectionReuseIfNecessary(); |
| HttpURLConnection urlConnection = null; |
| BufferedOutputStream out = null; |
| BufferedInputStream in = null; |
| |
| try { |
| final URL url = new URL(urlString); |
| urlConnection = (HttpURLConnection) url.openConnection(); |
| in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE); |
| out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE); |
| |
| int b; |
| while ((b = in.read()) != -1) { |
| out.write(b); |
| } |
| return true; |
| } catch (final IOException e) { |
| Log.e(TAG, "Error in downloadBitmap - " + e); |
| } finally { |
| if (urlConnection != null) { |
| urlConnection.disconnect(); |
| } |
| try { |
| if (out != null) { |
| out.close(); |
| } |
| if (in != null) { |
| in.close(); |
| } |
| } catch (final IOException e) {} |
| } |
| return false; |
| } |
| |
| /** |
| * Workaround for bug pre-Froyo, see here for more info: |
| * http://android-developers.blogspot.com/2011/09/androids-http-clients.html |
| */ |
| public static void disableConnectionReuseIfNecessary() { |
| // HTTP connection reuse which was buggy pre-froyo |
| if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) { |
| System.setProperty("http.keepAlive", "false"); |
| } |
| } |
| } |