Home | History | Annotate | Download | only in util
      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 
     17 package com.example.android.displayingbitmaps.util;
     18 
     19 import android.content.Context;
     20 import android.graphics.Bitmap;
     21 import android.net.ConnectivityManager;
     22 import android.net.NetworkInfo;
     23 import android.os.Build;
     24 import android.widget.Toast;
     25 
     26 import com.example.android.common.logger.Log;
     27 import com.example.android.displayingbitmaps.BuildConfig;
     28 import com.example.android.displayingbitmaps.R;
     29 
     30 import java.io.BufferedInputStream;
     31 import java.io.BufferedOutputStream;
     32 import java.io.File;
     33 import java.io.FileDescriptor;
     34 import java.io.FileInputStream;
     35 import java.io.IOException;
     36 import java.io.OutputStream;
     37 import java.net.HttpURLConnection;
     38 import java.net.URL;
     39 
     40 /**
     41  * A simple subclass of {@link ImageResizer} that fetches and resizes images fetched from a URL.
     42  */
     43 public class ImageFetcher extends ImageResizer {
     44     private static final String TAG = "ImageFetcher";
     45     private static final int HTTP_CACHE_SIZE = 10 * 1024 * 1024; // 10MB
     46     private static final String HTTP_CACHE_DIR = "http";
     47     private static final int IO_BUFFER_SIZE = 8 * 1024;
     48 
     49     private DiskLruCache mHttpDiskCache;
     50     private File mHttpCacheDir;
     51     private boolean mHttpDiskCacheStarting = true;
     52     private final Object mHttpDiskCacheLock = new Object();
     53     private static final int DISK_CACHE_INDEX = 0;
     54 
     55     /**
     56      * Initialize providing a target image width and height for the processing images.
     57      *
     58      * @param context
     59      * @param imageWidth
     60      * @param imageHeight
     61      */
     62     public ImageFetcher(Context context, int imageWidth, int imageHeight) {
     63         super(context, imageWidth, imageHeight);
     64         init(context);
     65     }
     66 
     67     /**
     68      * Initialize providing a single target image size (used for both width and height);
     69      *
     70      * @param context
     71      * @param imageSize
     72      */
     73     public ImageFetcher(Context context, int imageSize) {
     74         super(context, imageSize);
     75         init(context);
     76     }
     77 
     78     private void init(Context context) {
     79         checkConnection(context);
     80         mHttpCacheDir = ImageCache.getDiskCacheDir(context, HTTP_CACHE_DIR);
     81     }
     82 
     83     @Override
     84     protected void initDiskCacheInternal() {
     85         super.initDiskCacheInternal();
     86         initHttpDiskCache();
     87     }
     88 
     89     private void initHttpDiskCache() {
     90         if (!mHttpCacheDir.exists()) {
     91             mHttpCacheDir.mkdirs();
     92         }
     93         synchronized (mHttpDiskCacheLock) {
     94             if (ImageCache.getUsableSpace(mHttpCacheDir) > HTTP_CACHE_SIZE) {
     95                 try {
     96                     mHttpDiskCache = DiskLruCache.open(mHttpCacheDir, 1, 1, HTTP_CACHE_SIZE);
     97                     if (BuildConfig.DEBUG) {
     98                         Log.d(TAG, "HTTP cache initialized");
     99                     }
    100                 } catch (IOException e) {
    101                     mHttpDiskCache = null;
    102                 }
    103             }
    104             mHttpDiskCacheStarting = false;
    105             mHttpDiskCacheLock.notifyAll();
    106         }
    107     }
    108 
    109     @Override
    110     protected void clearCacheInternal() {
    111         super.clearCacheInternal();
    112         synchronized (mHttpDiskCacheLock) {
    113             if (mHttpDiskCache != null && !mHttpDiskCache.isClosed()) {
    114                 try {
    115                     mHttpDiskCache.delete();
    116                     if (BuildConfig.DEBUG) {
    117                         Log.d(TAG, "HTTP cache cleared");
    118                     }
    119                 } catch (IOException e) {
    120                     Log.e(TAG, "clearCacheInternal - " + e);
    121                 }
    122                 mHttpDiskCache = null;
    123                 mHttpDiskCacheStarting = true;
    124                 initHttpDiskCache();
    125             }
    126         }
    127     }
    128 
    129     @Override
    130     protected void flushCacheInternal() {
    131         super.flushCacheInternal();
    132         synchronized (mHttpDiskCacheLock) {
    133             if (mHttpDiskCache != null) {
    134                 try {
    135                     mHttpDiskCache.flush();
    136                     if (BuildConfig.DEBUG) {
    137                         Log.d(TAG, "HTTP cache flushed");
    138                     }
    139                 } catch (IOException e) {
    140                     Log.e(TAG, "flush - " + e);
    141                 }
    142             }
    143         }
    144     }
    145 
    146     @Override
    147     protected void closeCacheInternal() {
    148         super.closeCacheInternal();
    149         synchronized (mHttpDiskCacheLock) {
    150             if (mHttpDiskCache != null) {
    151                 try {
    152                     if (!mHttpDiskCache.isClosed()) {
    153                         mHttpDiskCache.close();
    154                         mHttpDiskCache = null;
    155                         if (BuildConfig.DEBUG) {
    156                             Log.d(TAG, "HTTP cache closed");
    157                         }
    158                     }
    159                 } catch (IOException e) {
    160                     Log.e(TAG, "closeCacheInternal - " + e);
    161                 }
    162             }
    163         }
    164     }
    165 
    166     /**
    167     * Simple network connection check.
    168     *
    169     * @param context
    170     */
    171     private void checkConnection(Context context) {
    172         final ConnectivityManager cm =
    173                 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    174         final NetworkInfo networkInfo = cm.getActiveNetworkInfo();
    175         if (networkInfo == null || !networkInfo.isConnectedOrConnecting()) {
    176             Toast.makeText(context, R.string.no_network_connection_toast, Toast.LENGTH_LONG).show();
    177             Log.e(TAG, "checkConnection - no connection found");
    178         }
    179     }
    180 
    181     /**
    182      * The main process method, which will be called by the ImageWorker in the AsyncTask background
    183      * thread.
    184      *
    185      * @param data The data to load the bitmap, in this case, a regular http URL
    186      * @return The downloaded and resized bitmap
    187      */
    188     private Bitmap processBitmap(String data) {
    189         if (BuildConfig.DEBUG) {
    190             Log.d(TAG, "processBitmap - " + data);
    191         }
    192 
    193         final String key = ImageCache.hashKeyForDisk(data);
    194         FileDescriptor fileDescriptor = null;
    195         FileInputStream fileInputStream = null;
    196         DiskLruCache.Snapshot snapshot;
    197         synchronized (mHttpDiskCacheLock) {
    198             // Wait for disk cache to initialize
    199             while (mHttpDiskCacheStarting) {
    200                 try {
    201                     mHttpDiskCacheLock.wait();
    202                 } catch (InterruptedException e) {}
    203             }
    204 
    205             if (mHttpDiskCache != null) {
    206                 try {
    207                     snapshot = mHttpDiskCache.get(key);
    208                     if (snapshot == null) {
    209                         if (BuildConfig.DEBUG) {
    210                             Log.d(TAG, "processBitmap, not found in http cache, downloading...");
    211                         }
    212                         DiskLruCache.Editor editor = mHttpDiskCache.edit(key);
    213                         if (editor != null) {
    214                             if (downloadUrlToStream(data,
    215                                     editor.newOutputStream(DISK_CACHE_INDEX))) {
    216                                 editor.commit();
    217                             } else {
    218                                 editor.abort();
    219                             }
    220                         }
    221                         snapshot = mHttpDiskCache.get(key);
    222                     }
    223                     if (snapshot != null) {
    224                         fileInputStream =
    225                                 (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
    226                         fileDescriptor = fileInputStream.getFD();
    227                     }
    228                 } catch (IOException e) {
    229                     Log.e(TAG, "processBitmap - " + e);
    230                 } catch (IllegalStateException e) {
    231                     Log.e(TAG, "processBitmap - " + e);
    232                 } finally {
    233                     if (fileDescriptor == null && fileInputStream != null) {
    234                         try {
    235                             fileInputStream.close();
    236                         } catch (IOException e) {}
    237                     }
    238                 }
    239             }
    240         }
    241 
    242         Bitmap bitmap = null;
    243         if (fileDescriptor != null) {
    244             bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, mImageWidth,
    245                     mImageHeight, getImageCache());
    246         }
    247         if (fileInputStream != null) {
    248             try {
    249                 fileInputStream.close();
    250             } catch (IOException e) {}
    251         }
    252         return bitmap;
    253     }
    254 
    255     @Override
    256     protected Bitmap processBitmap(Object data) {
    257         return processBitmap(String.valueOf(data));
    258     }
    259 
    260     /**
    261      * Download a bitmap from a URL and write the content to an output stream.
    262      *
    263      * @param urlString The URL to fetch
    264      * @return true if successful, false otherwise
    265      */
    266     public boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
    267         disableConnectionReuseIfNecessary();
    268         HttpURLConnection urlConnection = null;
    269         BufferedOutputStream out = null;
    270         BufferedInputStream in = null;
    271 
    272         try {
    273             final URL url = new URL(urlString);
    274             urlConnection = (HttpURLConnection) url.openConnection();
    275             in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE);
    276             out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);
    277 
    278             int b;
    279             while ((b = in.read()) != -1) {
    280                 out.write(b);
    281             }
    282             return true;
    283         } catch (final IOException e) {
    284             Log.e(TAG, "Error in downloadBitmap - " + e);
    285         } finally {
    286             if (urlConnection != null) {
    287                 urlConnection.disconnect();
    288             }
    289             try {
    290                 if (out != null) {
    291                     out.close();
    292                 }
    293                 if (in != null) {
    294                     in.close();
    295                 }
    296             } catch (final IOException e) {}
    297         }
    298         return false;
    299     }
    300 
    301     /**
    302      * Workaround for bug pre-Froyo, see here for more info:
    303      * http://android-developers.blogspot.com/2011/09/androids-http-clients.html
    304      */
    305     public static void disableConnectionReuseIfNecessary() {
    306         // HTTP connection reuse which was buggy pre-froyo
    307         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) {
    308             System.setProperty("http.keepAlive", "false");
    309         }
    310     }
    311 }
    312