Fix an OOM issue in mosaic blending.

Set two limits on when we will do mosaic blending.
1) mosaic_width * mosaic_height < (5 * frame_width) * (2 * frame_height)
2) mosaic_height < 2.5 *  frame_height

The latter limit rejects blending for the cases having too much movement
in the secondary direction.

Bug: 5554762
Change-Id: I05fb24bb1ff5446bea0cc1a1de8a02c37fb642d7
diff --git a/jni/feature_mos/src/mosaic/Blend.cpp b/jni/feature_mos/src/mosaic/Blend.cpp
index 18972ee..cce89ff 100644
--- a/jni/feature_mos/src/mosaic/Blend.cpp
+++ b/jni/feature_mos/src/mosaic/Blend.cpp
@@ -226,14 +226,25 @@
     yTopMost = max(0, max(yTopCorners[0], yTopCorners[1]) - fullRect.top + 1);
     yBottomMost = min(Mheight - 1, min(yBottomCorners[0], yBottomCorners[1]) - fullRect.top - 1);
 
+    if (xRightMost <= xLeftMost || yBottomMost <= yTopMost)
+    {
+        LOGE("RunBlend: aborting -consistency check failed,"
+             "(xLeftMost, xRightMost, yTopMost, yBottomMost): (%d, %d, %d, %d)",
+             xLeftMost, xRightMost, yTopMost, yBottomMost);
+        return BLEND_RET_ERROR;
+    }
+
     // Make sure image width is multiple of 4
     Mwidth = (unsigned short) ((Mwidth + 3) & ~3);
     Mheight = (unsigned short) ((Mheight + 3) & ~3);    // Round up.
 
-    if (Mwidth < width || Mheight < height || xRightMost <= xLeftMost)
+    ret = MosaicSizeCheck(LIMIT_SIZE_MULTIPLIER, LIMIT_HEIGHT_MULTIPLIER);
+    if (ret != BLEND_RET_OK)
     {
-        LOGE("RunBlend: aborting - consistency check failed, w=%d, h=%d, xLeftMost=%d, xRightMost=%d", Mwidth, Mheight, xLeftMost, xRightMost);
-        return BLEND_RET_ERROR;
+       LOGE("RunBlend: aborting - mosaic size check failed, "
+            "(frame_width, frame_height) vs (mosaic_width, mosaic_height): "
+            "(%d, %d) vs (%d, %d)", width, height, Mwidth, Mheight);
+       return ret;
     }
 
     LOGI("Allocate mosaic image for blending - size: %d x %d", Mwidth, Mheight);
@@ -296,6 +307,26 @@
     return ret;
 }
 
+int Blend::MosaicSizeCheck(float sizeMultiplier, float heightMultiplier) {
+   if (Mwidth < width || Mheight < height) {
+        return BLEND_RET_ERROR;
+    }
+
+   if ((Mwidth * Mheight) > (width * height * sizeMultiplier)) {
+         return BLEND_RET_ERROR;
+   }
+
+   // We won't do blending for the cases where users swing the device too much
+   // in the secondary direction. We use a short side to determine the
+   // secondary direction because users may hold the device in landsape
+   // or portrait.
+   int shortSide = min(Mwidth, Mheight);
+   if (shortSide > height * heightMultiplier) {
+       return BLEND_RET_ERROR;
+   }
+
+   return BLEND_RET_OK;
+}
 
 int Blend::FillFramePyramid(MosaicFrame *mb)
 {
diff --git a/jni/feature_mos/src/mosaic/Blend.h b/jni/feature_mos/src/mosaic/Blend.h
index 80bb577..ebb3bdc 100644
--- a/jni/feature_mos/src/mosaic/Blend.h
+++ b/jni/feature_mos/src/mosaic/Blend.h
@@ -113,6 +113,11 @@
 
   int  PerformFinalBlending(YUVinfo &imgMos, MosaicRect &cropping_rect);
   void CropFinalMosaic(YUVinfo &imgMos, MosaicRect &cropping_rect);
+
+private:
+   static const float LIMIT_SIZE_MULTIPLIER = 5.0f * 2.0f;
+   static const float LIMIT_HEIGHT_MULTIPLIER = 2.5f;
+   int MosaicSizeCheck(float sizeMultiplier, float heightMultiplier);
 };
 
 #endif
diff --git a/src/com/android/camera/panorama/PanoramaActivity.java b/src/com/android/camera/panorama/PanoramaActivity.java
index 8b9c65a..a65d263 100755
--- a/src/com/android/camera/panorama/PanoramaActivity.java
+++ b/src/com/android/camera/panorama/PanoramaActivity.java
@@ -989,9 +989,20 @@
         keepScreenOnAwhile();
     }
 
+    /**
+     * Generate the final mosaic image.
+     *
+     * @param highRes flag to indicate whether we want to get a high-res version.
+     * @return a MosaicJpeg with its isValid flag set to true if successful; null if the generation
+     *         process is cancelled; and a MosaicJpeg with its isValid flag set to false if there
+     *         is an error in generating the final mosaic.
+     */
     public MosaicJpeg generateFinalMosaic(boolean highRes) {
-        if (mMosaicFrameProcessor.createMosaic(highRes) == Mosaic.MOSAIC_RET_CANCELLED) {
+        int mosaicReturnCode = mMosaicFrameProcessor.createMosaic(highRes);
+        if (mosaicReturnCode == Mosaic.MOSAIC_RET_CANCELLED) {
             return null;
+        } else if (mosaicReturnCode == Mosaic.MOSAIC_RET_ERROR) {
+            return new MosaicJpeg();
         }
 
         byte[] imageData = mMosaicFrameProcessor.getFinalMosaicNV21();