Bug 863154 - Part 1: Catch Bitmap OOMs when decoding streams. r=mfinkle
authorChris Peterson <cpeterson@mozilla.com>
Tue, 07 May 2013 18:29:48 -0700
changeset 142251 814044b7f85345868cdcc9739bb9d86512d9564c
parent 142250 061c33f0e98409ec930948d9e79bb7d412830c57
child 142252 ae87f942ac08bca3010133ee7901ce32e52551bf
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle
bugs863154
milestone23.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 863154 - Part 1: Catch Bitmap OOMs when decoding streams. r=mfinkle
mobile/android/base/AlertNotification.java
mobile/android/base/Favicons.java
mobile/android/base/LightweightTheme.java
mobile/android/base/gfx/BitmapUtils.java
--- a/mobile/android/base/AlertNotification.java
+++ b/mobile/android/base/AlertNotification.java
@@ -1,25 +1,25 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
+import org.mozilla.gecko.gfx.BitmapUtils;
+
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.Context;
 import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
 import android.net.Uri;
 import android.util.Log;
 import android.widget.RemoteViews;
 
-import java.net.URL;
 import java.text.NumberFormat;
 
 public class AlertNotification
     extends Notification
 {
     private static final String LOGTAG = "GeckoAlertNotification";
 
     private final int mId;
@@ -49,18 +49,17 @@ public class AlertNotification
 
         if (aIconUri == null || aIconUri.getScheme() == null)
             return;
 
         // Custom view
         int layout = R.layout.notification_icon_text;
         RemoteViews view = new RemoteViews(mContext.getPackageName(), layout);
         try {
-            URL url = new URL(aIconUri.toString());
-            Bitmap bm = BitmapFactory.decodeStream(url.openStream());
+            Bitmap bm = BitmapUtils.decodeUrl(aIconUri);
             if (bm == null) {
                 Log.e(LOGTAG, "failed to decode icon");
                 return;
             }
             view.setImageViewBitmap(R.id.notification_image, bm);
             view.setTextViewText(R.id.notification_title, mTitle);
             if (mText.length() > 0) {
                 view.setTextViewText(R.id.notification_text, mText);
--- a/mobile/android/base/Favicons.java
+++ b/mobile/android/base/Favicons.java
@@ -14,17 +14,16 @@ import org.mozilla.gecko.util.UiAsyncTas
 import org.apache.http.HttpEntity;
 import org.apache.http.HttpResponse;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.entity.BufferedHttpEntity;
 
 import android.content.ContentResolver;
 import android.content.Context;
 import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
 import android.net.http.AndroidHttpClient;
 import android.os.Handler;
 import android.support.v4.util.LruCache;
 import android.util.Log;
 
 import java.io.InputStream;
 import java.net.MalformedURLException;
 import java.net.URI;
@@ -324,17 +323,18 @@ public class Favicons {
                     // Is the content type valid? Might be a captive portal.
                     String contentType = entity.getContentType().getValue();
                     if (contentType.indexOf("image") == -1)
                         return null;
                 }
 
                 BufferedHttpEntity bufferedEntity = new BufferedHttpEntity(entity);
                 InputStream contentStream = bufferedEntity.getContent();
-                image = BitmapFactory.decodeStream(contentStream);
+                image = BitmapUtils.decodeStream(contentStream);
+                contentStream.close();
             } catch (Exception e) {
                 Log.e(LOGTAG, "Error reading favicon", e);
             }
 
             return image;
         }
 
         @Override
--- a/mobile/android/base/LightweightTheme.java
+++ b/mobile/android/base/LightweightTheme.java
@@ -7,36 +7,31 @@ package org.mozilla.gecko;
 
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.util.GeckoEventListener;
 
 import org.json.JSONObject;
 
 import android.app.Application;
 import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.Shader;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewParent;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
 import java.util.ArrayList;
 import java.util.List;
 
 public class LightweightTheme implements GeckoEventListener {
     private static final String LOGTAG = "GeckoLightweightTheme";
 
     private Application mApplication;
     private Handler mHandler;
@@ -79,31 +74,25 @@ public class LightweightTheme implements
     public void handleMessage(String event, JSONObject message) {
         try {
             if (event.equals("LightweightTheme:Update")) {
                 JSONObject lightweightTheme = message.getJSONObject("data");
                 String headerURL = lightweightTheme.getString("headerURL"); 
                 int mark = headerURL.indexOf('?');
                 if (mark != -1)
                     headerURL = headerURL.substring(0, mark);
-                try {
-                    // Get the image and convert it to a bitmap.
-                    URL url = new URL(headerURL);
-                    InputStream stream = url.openStream();
-                    final Bitmap bitmap = BitmapFactory.decodeStream(stream);
-                    stream.close();
-                    mHandler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            setLightweightTheme(bitmap);
-                        }
-                    });
-                } catch(MalformedURLException e) {
-                } catch(IOException e) {
-                }
+
+                // Get the image and convert it to a bitmap.
+                final Bitmap bitmap = BitmapUtils.decodeUrl(headerURL);
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        setLightweightTheme(bitmap);
+                    }
+                });
             } else if (event.equals("LightweightTheme:Disable")) {
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
                         resetLightweightTheme();
                     }
                 });
             }
--- a/mobile/android/base/gfx/BitmapUtils.java
+++ b/mobile/android/base/gfx/BitmapUtils.java
@@ -3,19 +3,25 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.gfx;
 
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Color;
+import android.net.Uri;
 import android.util.Base64;
 import android.util.Log;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+
 public final class BitmapUtils {
     private static final String LOGTAG = "GeckoBitmapUtils";
 
     private BitmapUtils() {}
 
     public static Bitmap decodeByteArray(byte[] bytes) {
         return decodeByteArray(bytes, null);
     }
@@ -45,16 +51,68 @@ public final class BitmapUtils {
                           + "a bitmap with dimensions " + bitmap.getWidth()
                           + "x" + bitmap.getHeight());
             return null;
         }
 
         return bitmap;
     }
 
+    public static Bitmap decodeStream(InputStream inputStream) {
+        try {
+            return BitmapFactory.decodeStream(inputStream);
+        } catch (OutOfMemoryError e) {
+            Log.e(LOGTAG, "decodeStream() OOM!", e);
+            return null;
+        }
+    }
+
+    public static Bitmap decodeUrl(Uri uri) {
+        return decodeUrl(uri.toString());
+    }
+
+    public static Bitmap decodeUrl(String urlString) {
+        URL url;
+
+        try {
+            url = new URL(urlString);
+        } catch(MalformedURLException e) {
+            Log.w(LOGTAG, "decodeUrl: malformed URL " + urlString);
+            return null;
+        }
+
+        return decodeUrl(url);
+    }
+
+    public static Bitmap decodeUrl(URL url) {
+        InputStream stream = null;
+
+        try {
+            stream = url.openStream();
+        } catch(IOException e) {
+            Log.w(LOGTAG, "decodeUrl: IOException downloading " + url);
+            return null;
+        }
+
+        if (stream == null) {
+            Log.w(LOGTAG, "decodeUrl: stream not found downloading " + url);
+            return null;
+        }
+
+        Bitmap bitmap = decodeStream(stream);
+
+        try {
+            stream.close();
+        } catch(IOException e) {
+            Log.w(LOGTAG, "decodeUrl: IOException closing stream " + url, e);
+        }
+
+        return bitmap;
+    }
+
     public static int getDominantColor(Bitmap source) {
         return getDominantColor(source, true);
     }
 
     public static int getDominantColor(Bitmap source, boolean applyThreshold) {
       if (source == null)
         return Color.argb(255,255,255,255);