Dbrant has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/319063

Change subject: Fix possible leak in FacePostprocessor.
......................................................................

Fix possible leak in FacePostprocessor.

Observed in API 17, this leak seems to happen because the
FacePostprocessor class implicitly retains an instance of the
FaceAndColorDetectImageView of which it is a member.

This patch separates out the FacePostprocessor class, along with its
concerns, and makes sure that it doesn't keep any references to the View
that uses it.

Change-Id: I6e637b4e5fe676f9b6bc13f1e2ff21d1c500bbaa
---
M app/src/main/java/org/wikipedia/views/FaceAndColorDetectImageView.java
A app/src/main/java/org/wikipedia/views/FacePostprocessor.java
2 files changed, 115 insertions(+), 92 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/apps/android/wikipedia 
refs/changes/63/319063/1

diff --git 
a/app/src/main/java/org/wikipedia/views/FaceAndColorDetectImageView.java 
b/app/src/main/java/org/wikipedia/views/FaceAndColorDetectImageView.java
index 0e7ba67..c8d6ad8 100644
--- a/app/src/main/java/org/wikipedia/views/FaceAndColorDetectImageView.java
+++ b/app/src/main/java/org/wikipedia/views/FaceAndColorDetectImageView.java
@@ -1,41 +1,28 @@
 package org.wikipedia.views;
 
 import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
 import android.graphics.PointF;
-import android.graphics.Rect;
-import android.media.FaceDetector;
 import android.net.Uri;
 import android.support.annotation.ColorInt;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
-import android.support.v7.graphics.Palette;
 import android.util.AttributeSet;
 
 import com.facebook.drawee.backends.pipeline.Fresco;
 import com.facebook.drawee.interfaces.DraweeController;
 import com.facebook.drawee.view.SimpleDraweeView;
-import com.facebook.imagepipeline.request.BasePostprocessor;
 import com.facebook.imagepipeline.request.ImageRequest;
 import com.facebook.imagepipeline.request.ImageRequestBuilder;
 
-import org.wikipedia.R;
 import org.wikipedia.WikipediaApp;
-import org.wikipedia.util.MathUtil;
-import org.wikipedia.util.log.L;
 
 public class FaceAndColorDetectImageView extends SimpleDraweeView {
-    private static final int BITMAP_COPY_WIDTH = 200;
 
     public interface OnImageLoadListener {
         void onImageLoaded(int bmpHeight, @Nullable PointF faceLocation, 
@ColorInt int mainColor);
         void onImageFailed();
     }
 
-    private FacePostprocessor facePostprocessor = new FacePostprocessor();
     @NonNull private OnImageLoadListener listener = new DefaultListener();
 
     public FaceAndColorDetectImageView(Context context) {
@@ -61,91 +48,13 @@
         }
         ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
                 .setProgressiveRenderingEnabled(true)
-                .setPostprocessor(facePostprocessor)
+                .setPostprocessor(new FacePostprocessor(listener))
                 .build();
         DraweeController controller = Fresco.newDraweeControllerBuilder()
                         .setImageRequest(request)
                         .setAutoPlayAnimations(true)
                         .build();
         setController(controller);
-    }
-
-    @Nullable private PointF detectFace(Bitmap testBitmap) {
-        final int maxFaces = 1;
-        long millis = System.currentTimeMillis();
-        // initialize the face detector, and look for only one face...
-        FaceDetector fd = new FaceDetector(testBitmap.getWidth(), 
testBitmap.getHeight(), maxFaces);
-        FaceDetector.Face[] faces = new FaceDetector.Face[maxFaces];
-        int numFound = fd.findFaces(testBitmap, faces);
-        PointF facePos = null;
-        if (numFound > 0) {
-            facePos = new PointF();
-            faces[0].getMidPoint(facePos);
-            // center on the nose, not on the eyes
-            facePos.y += faces[0].eyesDistance() / 2;
-            // normalize the position to [0, 1]
-            facePos.set(MathUtil.constrain(facePos.x / testBitmap.getWidth(), 
0, 1),
-                    MathUtil.constrain(facePos.y / testBitmap.getHeight(), 0, 
1));
-            L.d("Found face at " + facePos.x + ", " + facePos.y);
-        }
-        L.d("Face detection took " + (System.currentTimeMillis() - millis) + 
"ms");
-        return facePos;
-    }
-
-    @NonNull private Bitmap new565ScaledBitmap(Bitmap src) {
-        Bitmap copy =  Bitmap.createBitmap(BITMAP_COPY_WIDTH,
-                (src.getHeight() * BITMAP_COPY_WIDTH) / src.getWidth(), 
Bitmap.Config.RGB_565);
-        Canvas canvas = new Canvas(copy);
-        Rect srcRect = new Rect(0, 0, src.getWidth(), src.getHeight());
-        Rect destRect = new Rect(0, 0, BITMAP_COPY_WIDTH, copy.getHeight());
-        Paint paint = new Paint();
-        paint.setColor(Color.BLACK);
-        canvas.drawBitmap(src, srcRect, destRect, paint);
-        return copy;
-    }
-
-    @ColorInt private int extractMainColor(Palette colorPalette, @ColorInt int 
defaultColor) {
-        int mainColor = defaultColor;
-        if (colorPalette.getDarkMutedSwatch() != null) {
-            mainColor = colorPalette.getDarkMutedSwatch().getRgb();
-        } else if (colorPalette.getDarkVibrantSwatch() != null) {
-            mainColor = colorPalette.getDarkVibrantSwatch().getRgb();
-        }
-        return mainColor;
-    }
-
-    private boolean isBitmapEligibleForImageProcessing(Bitmap bitmap) {
-        final int minSize = 64;
-        return bitmap.getWidth() >= minSize && bitmap.getHeight() >= minSize;
-    }
-
-    private class FacePostprocessor extends BasePostprocessor {
-        @Override
-        public String getName() {
-            return "FacePostprocessor";
-        }
-
-        @Override
-        public void process(Bitmap destBitmap, Bitmap sourceBitmap) {
-            if (isBitmapEligibleForImageProcessing(sourceBitmap)) {
-                copyWithWhiteBackground(destBitmap, sourceBitmap);
-                Bitmap testBmp = new565ScaledBitmap(sourceBitmap);
-                Palette colorPalette = Palette.from(testBmp).generate();
-                PointF facePos = detectFace(testBmp);
-                int defaultColor = 
getContext().getResources().getColor(R.color.dark_gray);
-                listener.onImageLoaded(destBitmap.getHeight(), facePos, 
extractMainColor(colorPalette, defaultColor));
-            } else {
-                listener.onImageFailed();
-            }
-        }
-
-        private void copyWithWhiteBackground(Bitmap destBitmap, Bitmap 
sourceBitmap) {
-            Canvas canvas = new Canvas(destBitmap);
-            Paint backgroundPaint = new Paint();
-            backgroundPaint.setColor(Color.WHITE);
-            canvas.drawRect(0f, 0f, destBitmap.getWidth(), 
destBitmap.getHeight(), backgroundPaint);
-            canvas.drawBitmap(sourceBitmap, 0f, 0f, backgroundPaint);
-        }
     }
 
     private class DefaultListener implements OnImageLoadListener {
diff --git a/app/src/main/java/org/wikipedia/views/FacePostprocessor.java 
b/app/src/main/java/org/wikipedia/views/FacePostprocessor.java
new file mode 100644
index 0000000..1c41b87
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/views/FacePostprocessor.java
@@ -0,0 +1,114 @@
+package org.wikipedia.views;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.media.FaceDetector;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.graphics.Palette;
+
+import com.facebook.imagepipeline.request.BasePostprocessor;
+
+import org.wikipedia.R;
+import org.wikipedia.WikipediaApp;
+import org.wikipedia.util.MathUtil;
+import org.wikipedia.util.log.L;
+
+import java.lang.ref.WeakReference;
+
+public class FacePostprocessor extends BasePostprocessor {
+    private static final int BITMAP_COPY_WIDTH = 200;
+
+    @NonNull
+    private WeakReference<FaceAndColorDetectImageView.OnImageLoadListener> 
listener;
+
+    public FacePostprocessor(@NonNull 
FaceAndColorDetectImageView.OnImageLoadListener listener) {
+        this.listener = new WeakReference<>(listener);
+    }
+
+    @Override public String getName() {
+        return "FacePostprocessor";
+    }
+
+    @Override public void process(Bitmap destBitmap, Bitmap sourceBitmap) {
+        if (listener.get() == null) {
+            return;
+        }
+        if (isBitmapEligibleForImageProcessing(sourceBitmap)) {
+            copyWithWhiteBackground(destBitmap, sourceBitmap);
+            Bitmap testBmp = new565ScaledBitmap(sourceBitmap);
+            Palette colorPalette = Palette.from(testBmp).generate();
+            PointF facePos = detectFace(testBmp);
+            int defaultColor = 
ContextCompat.getColor(WikipediaApp.getInstance(), R.color.dark_gray);
+            listener.get().onImageLoaded(destBitmap.getHeight(), facePos,
+                    extractMainColor(colorPalette, defaultColor));
+        } else {
+            listener.get().onImageFailed();
+        }
+    }
+
+    @Nullable private static PointF detectFace(@NonNull Bitmap testBitmap) {
+        final int maxFaces = 1;
+        long millis = System.currentTimeMillis();
+        // initialize the face detector, and look for only one face...
+        FaceDetector fd = new FaceDetector(testBitmap.getWidth(), 
testBitmap.getHeight(), maxFaces);
+        FaceDetector.Face[] faces = new FaceDetector.Face[maxFaces];
+        int numFound = fd.findFaces(testBitmap, faces);
+        PointF facePos = null;
+        if (numFound > 0) {
+            facePos = new PointF();
+            faces[0].getMidPoint(facePos);
+            // center on the nose, not on the eyes
+            facePos.y += faces[0].eyesDistance() / 2;
+            // normalize the position to [0, 1]
+            facePos.set(MathUtil.constrain(facePos.x / testBitmap.getWidth(), 
0, 1),
+                    MathUtil.constrain(facePos.y / testBitmap.getHeight(), 0, 
1));
+            L.d("Found face at " + facePos.x + ", " + facePos.y);
+        }
+        L.d("Face detection took " + (System.currentTimeMillis() - millis) + 
"ms");
+        return facePos;
+    }
+
+    @NonNull private static Bitmap new565ScaledBitmap(@NonNull Bitmap src) {
+        Bitmap copy =  Bitmap.createBitmap(BITMAP_COPY_WIDTH,
+                (src.getHeight() * BITMAP_COPY_WIDTH) / src.getWidth(), 
Bitmap.Config.RGB_565);
+        Canvas canvas = new Canvas(copy);
+        Rect srcRect = new Rect(0, 0, src.getWidth(), src.getHeight());
+        Rect destRect = new Rect(0, 0, BITMAP_COPY_WIDTH, copy.getHeight());
+        Paint paint = new Paint();
+        paint.setColor(Color.BLACK);
+        canvas.drawBitmap(src, srcRect, destRect, paint);
+        return copy;
+    }
+
+    @ColorInt private static int extractMainColor(@NonNull Palette 
colorPalette,
+                                        @ColorInt int defaultColor) {
+        int mainColor = defaultColor;
+        if (colorPalette.getDarkMutedSwatch() != null) {
+            mainColor = colorPalette.getDarkMutedSwatch().getRgb();
+        } else if (colorPalette.getDarkVibrantSwatch() != null) {
+            mainColor = colorPalette.getDarkVibrantSwatch().getRgb();
+        }
+        return mainColor;
+    }
+
+    private static boolean isBitmapEligibleForImageProcessing(@NonNull Bitmap 
bitmap) {
+        final int minSize = 64;
+        return bitmap.getWidth() >= minSize && bitmap.getHeight() >= minSize;
+    }
+
+    private static void copyWithWhiteBackground(@NonNull Bitmap destBitmap,
+                                                @NonNull Bitmap sourceBitmap) {
+        Canvas canvas = new Canvas(destBitmap);
+        Paint backgroundPaint = new Paint();
+        backgroundPaint.setColor(Color.WHITE);
+        canvas.drawRect(0f, 0f, destBitmap.getWidth(), destBitmap.getHeight(), 
backgroundPaint);
+        canvas.drawBitmap(sourceBitmap, 0f, 0f, backgroundPaint);
+    }
+}

-- 
To view, visit https://gerrit.wikimedia.org/r/319063
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I6e637b4e5fe676f9b6bc13f1e2ff21d1c500bbaa
Gerrit-PatchSet: 1
Gerrit-Project: apps/android/wikipedia
Gerrit-Branch: master
Gerrit-Owner: Dbrant <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to