Merge "Show the selected state on an ad on two pane" into jb-ub-mail-ur10
diff --git a/src/com/android/bitmap/DecodeTask.java b/src/com/android/bitmap/DecodeTask.java
index af4674e..2210ec6 100644
--- a/src/com/android/bitmap/DecodeTask.java
+++ b/src/com/android/bitmap/DecodeTask.java
@@ -8,6 +8,9 @@
import android.os.AsyncTask;
+import com.android.ex.photo.util.Exif;
+import com.android.mail.utils.RectUtils;
+
import java.io.IOException;
import java.io.InputStream;
@@ -94,7 +97,10 @@
AssetFileDescriptor fd = null;
InputStream in = null;
try {
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
+ final boolean isJellyBeanOrAbove = android.os.Build.VERSION.SDK_INT
+ >= android.os.Build.VERSION_CODES.JELLY_BEAN;
+ // This blocks during fling when the pool is empty. We block early to avoid jank.
+ if (isJellyBeanOrAbove) {
Trace.beginSection("poll for reusable bitmap");
mInBitmap = mCache.poll();
Trace.endSection();
@@ -104,17 +110,65 @@
}
}
- Trace.beginSection("create fd or stream");
+ Trace.beginSection("create fd and stream");
fd = mKey.createFd();
+ Trace.endSection();
if (fd == null) {
- in = mKey.createInputStream();
+ in = reset(in);
+ if (in == null) {
+ return null;
+ }
+ }
+
+ Trace.beginSection("get bytesize");
+ final long byteSize;
+ if (fd != null) {
+ byteSize = fd.getLength();
+ } else {
+ byteSize = -1;
}
Trace.endSection();
+ Trace.beginSection("get orientation");
+ if (fd != null) {
+ // Creating an input stream from the file descriptor makes it useless afterwards.
+ Trace.beginSection("create fd and stream");
+ final AssetFileDescriptor orientationFd = mKey.createFd();
+ in = orientationFd.createInputStream();
+ Trace.endSection();
+ }
+ final int orientation = Exif.getOrientation(in, byteSize);
+ if (fd != null) {
+ try {
+ // Close the temporary file descriptor.
+ in.close();
+ } catch (IOException ex) {
+ }
+ }
+ final boolean isNotRotatedOr180 = orientation == 0 || orientation == 180;
+ Trace.endSection();
+
+ if (orientation != 0) {
+ // disable inBitmap-- bitmap reuse doesn't work with different decode regions due
+ // to orientation
+ if (mInBitmap != null) {
+ mCache.offer(mInBitmap);
+ mInBitmap = null;
+ mOpts.inBitmap = null;
+ }
+ }
+
if (isCancelled()) {
return null;
}
+ if (fd == null) {
+ in = reset(in);
+ if (in == null) {
+ return null;
+ }
+ }
+
Trace.beginSection("decodeBounds");
mOpts.inJustDecodeBounds = true;
if (fd != null) {
@@ -128,20 +182,25 @@
return null;
}
- final int srcW = mOpts.outWidth;
- final int srcH = mOpts.outHeight;
-
+ // We want to calculate the sample size "as if" the orientation has been corrected.
+ final int srcW, srcH; // Orientation corrected.
+ if (isNotRotatedOr180) {
+ srcW = mOpts.outWidth;
+ srcH = mOpts.outHeight;
+ } else {
+ srcW = mOpts.outHeight;
+ srcH = mOpts.outWidth;
+ }
+ mOpts.inSampleSize = calculateSampleSize(srcW, srcH, mDestW, mDestH);
mOpts.inJustDecodeBounds = false;
mOpts.inMutable = true;
- mOpts.inSampleSize = calculateSampleSize(srcW, srcH, mDestW, mDestH);
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
+ if (isJellyBeanOrAbove && orientation == 0) {
if (mInBitmap == null) {
if (DEBUG) System.err.println(
"decode thread wants a bitmap. cache dump:\n" + mCache.toDebugString());
Trace.beginSection("create reusable bitmap");
- mInBitmap = new ReusableBitmap(
- Bitmap.createBitmap(mDestBufferW, mDestBufferH,
- Bitmap.Config.ARGB_8888));
+ mInBitmap = new ReusableBitmap(Bitmap.createBitmap(mDestBufferW, mDestBufferH,
+ Bitmap.Config.ARGB_8888));
Trace.endSection();
if (isCancelled()) {
@@ -158,21 +217,23 @@
mOpts.inBitmap = mInBitmap.bmp;
}
- Bitmap decodeResult = null;
-
- if (in != null) {
- in = mKey.createInputStream();
- }
-
if (isCancelled()) {
return null;
}
- final Rect srcRect = new Rect();
+ if (fd == null) {
+ in = reset(in);
+ if (in == null) {
+ return null;
+ }
+ }
+
+ Bitmap decodeResult = null;
+ final Rect srcRect = new Rect(); // Not orientation corrected. True coordinates.
if (CROP_DURING_DECODE) {
try {
Trace.beginSection("decodeCropped" + mOpts.inSampleSize);
- decodeResult = decodeCropped(fd, in, srcRect);
+ decodeResult = decodeCropped(fd, in, orientation, srcRect);
} catch (IOException e) {
// fall through to below and try again with the non-cropping decoder
e.printStackTrace();
@@ -213,36 +274,33 @@
}
}
- if (decodeResult != null) {
- if (mInBitmap != null) {
- result = mInBitmap;
- // srcRect is non-empty when using the cropping BitmapRegionDecoder codepath
- if (!srcRect.isEmpty()) {
- result.setLogicalWidth((srcRect.right - srcRect.left) / mOpts.inSampleSize);
- result.setLogicalHeight(
- (srcRect.bottom - srcRect.top) / mOpts.inSampleSize);
- } else {
- result.setLogicalWidth(mOpts.outWidth);
- result.setLogicalHeight(mOpts.outHeight);
- }
+ if (decodeResult == null) {
+ return null;
+ }
+
+ if (mInBitmap != null) {
+ result = mInBitmap;
+ // srcRect is non-empty when using the cropping BitmapRegionDecoder codepath
+ if (!srcRect.isEmpty()) {
+ result.setLogicalWidth((srcRect.right - srcRect.left) / mOpts.inSampleSize);
+ result.setLogicalHeight(
+ (srcRect.bottom - srcRect.top) / mOpts.inSampleSize);
} else {
- // no mInBitmap means no pooling
- result = new ReusableBitmap(decodeResult, false /* reusable */);
+ result.setLogicalWidth(mOpts.outWidth);
+ result.setLogicalHeight(mOpts.outHeight);
+ }
+ } else {
+ // no mInBitmap means no pooling
+ result = new ReusableBitmap(decodeResult, false /* reusable */);
+ if (isNotRotatedOr180) {
result.setLogicalWidth(decodeResult.getWidth());
result.setLogicalHeight(decodeResult.getHeight());
+ } else {
+ result.setLogicalWidth(decodeResult.getHeight());
+ result.setLogicalHeight(decodeResult.getWidth());
}
- // System.out.println("*** async task decoded fd=" + mUri +
- // " to" +
- // " sz=" + (decodeResult.getByteCount() >> 10) + "KB dstW/H=" +
- // result.getLogicalWidth() +
- // "/" + result.getLogicalHeight() + " srcW/H=" + srcW + "/" +
- // srcH + " ss=" +
- // mOpts.inSampleSize + " mutable=" + decodeResult.isMutable() +
- // " matchesInBitmap=" + (mOpts.inBitmap == decodeResult));
- } else {
- // System.out.println("*** async task cancelled decode of fd=" +
- // mUri);
}
+ result.setOrientation(orientation);
} catch (Exception e) {
e.printStackTrace();
} finally {
@@ -250,14 +308,12 @@
try {
fd.close();
} catch (IOException e) {
- e.printStackTrace();
}
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
- e.printStackTrace();
}
}
if (result != null) {
@@ -274,40 +330,72 @@
return result;
}
- private Bitmap decodeCropped(AssetFileDescriptor fd, InputStream in, Rect outSrcRect)
- throws IOException {
+ private Bitmap decodeCropped(final AssetFileDescriptor fd, final InputStream in,
+ final int orientation, final Rect outSrcRect) throws IOException {
final BitmapRegionDecoder brd;
if (fd != null) {
- brd = BitmapRegionDecoder.newInstance(fd.getFileDescriptor(),
- true /* shareable */);
+ brd = BitmapRegionDecoder.newInstance(fd.getFileDescriptor(), true /* shareable */);
} else {
- brd = BitmapRegionDecoder.newInstance(in,
- true /* shareable */);
+ brd = BitmapRegionDecoder.newInstance(in, true /* shareable */);
}
if (isCancelled()) {
brd.recycle();
return null;
}
- final int srcW = mOpts.outWidth;
- final int srcH = mOpts.outHeight;
+ // We want to call calculateCroppedSrcRect() on the source rectangle "as if" the
+ // orientation has been corrected.
+ final int srcW, srcH; //Orientation corrected.
+ final boolean isNotRotatedOr180 = orientation == 0 || orientation == 180;
+ if (isNotRotatedOr180) {
+ srcW = mOpts.outWidth;
+ srcH = mOpts.outHeight;
+ } else {
+ srcW = mOpts.outHeight;
+ srcH = mOpts.outWidth;
+ }
- // Trace.beginSection("DecodeRegionGetDimens");
- // final int tmpw = brd.getWidth();
- // final int tmph = brd.getHeight();
- // Trace.endSection();
-
- // Center the decode on the top 1/3
+ // Coordinates are orientation corrected.
+ // Center the decode on the top 1/3.
BitmapUtils.calculateCroppedSrcRect(srcW, srcH, mDestW, mDestH, mDestH, mOpts.inSampleSize,
1f / 3, true /* absoluteFraction */, 1f, outSrcRect);
if (DEBUG) System.out.println("rect for this decode is: " + outSrcRect
+ " srcW/H=" + srcW + "/" + srcH
+ " dstW/H=" + mDestW + "/" + mDestH);
+
+ // calculateCroppedSrcRect() gave us the source rectangle "as if" the orientation has
+ // been corrected. We need to decode the uncorrected source rectangle. Calculate true
+ // coordinates.
+ RectUtils.rotateRectForOrientation(orientation, new Rect(0, 0, srcW, srcH), outSrcRect);
+
final Bitmap result = brd.decodeRegion(outSrcRect, mOpts);
brd.recycle();
return result;
}
+ /**
+ * Return an input stream that can be read from the beginning using the most efficient way,
+ * given an input stream that may or may not support reset(), or given null.
+ *
+ * The returned input stream may or may not be the same stream.
+ */
+ private InputStream reset(InputStream in) throws IOException {
+ Trace.beginSection("create stream");
+ if (in == null) {
+ in = mKey.createInputStream();
+ } else if (in.markSupported()) {
+ in.reset();
+ } else {
+ try {
+ in.close();
+ } catch (IOException ex) {
+ }
+ in = mKey.createInputStream();
+ }
+ Trace.endSection();
+ return in;
+ }
+
private Bitmap decode(AssetFileDescriptor fd, InputStream in) {
final Bitmap result;
if (fd != null) {
diff --git a/src/com/android/bitmap/ReusableBitmap.java b/src/com/android/bitmap/ReusableBitmap.java
index 846eda0..2e0199f 100644
--- a/src/com/android/bitmap/ReusableBitmap.java
+++ b/src/com/android/bitmap/ReusableBitmap.java
@@ -27,14 +27,16 @@
public final Bitmap bmp;
private int mWidth;
private int mHeight;
+ private int mOrientation;
+
private int mRefCount = 0;
private final boolean mReusable;
- public ReusableBitmap(Bitmap bitmap) {
+ public ReusableBitmap(final Bitmap bitmap) {
this(bitmap, true /* reusable */);
}
- public ReusableBitmap(Bitmap bitmap, boolean reusable) {
+ public ReusableBitmap(final Bitmap bitmap, final boolean reusable) {
bmp = bitmap;
mReusable = reusable;
}
@@ -60,6 +62,14 @@
return mHeight;
}
+ public int getOrientation() {
+ return mOrientation;
+ }
+
+ public void setOrientation(final int orientation) {
+ mOrientation = orientation;
+ }
+
public int getByteCount() {
return bmp.getByteCount();
}
diff --git a/src/com/android/mail/bitmap/AttachmentDrawable.java b/src/com/android/mail/bitmap/AttachmentDrawable.java
index 927561a..f350caa 100644
--- a/src/com/android/mail/bitmap/AttachmentDrawable.java
+++ b/src/com/android/mail/bitmap/AttachmentDrawable.java
@@ -27,6 +27,7 @@
import com.android.mail.browse.ConversationItemViewCoordinates;
import com.android.mail.ui.SwipeableListView;
import com.android.mail.utils.LogUtils;
+import com.android.mail.utils.RectUtils;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
@@ -194,7 +195,7 @@
}
@Override
- public void draw(Canvas canvas) {
+ public void draw(final Canvas canvas) {
final Rect bounds = getBounds();
if (bounds.isEmpty()) {
return;
@@ -207,7 +208,24 @@
mCoordinates.attachmentPreviewsDecodeHeight, Integer.MAX_VALUE,
mParallaxFraction, false /* absoluteFraction */,
mParallaxSpeedMultiplier, mSrcRect);
- canvas.drawBitmap(mBitmap.bmp, mSrcRect, bounds, mPaint);
+
+ final int orientation = mBitmap.getOrientation();
+ // calculateCroppedSrcRect() gave us the source rectangle "as if" the orientation has
+ // been corrected. We need to decode the uncorrected source rectangle. Calculate true
+ // coordinates.
+ RectUtils.rotateRectForOrientation(orientation,
+ new Rect(0, 0, mBitmap.getLogicalWidth(), mBitmap.getLogicalHeight()),
+ mSrcRect);
+
+ // We may need to rotate the canvas, so we also have to rotate the bounds.
+ final Rect rotatedBounds = new Rect(bounds);
+ RectUtils.rotateRect(orientation, bounds.centerX(), bounds.centerY(), rotatedBounds);
+
+ // Rotate the canvas.
+ canvas.save();
+ canvas.rotate(orientation, bounds.centerX(), bounds.centerY());
+ canvas.drawBitmap(mBitmap.bmp, mSrcRect, rotatedBounds, mPaint);
+ canvas.restore();
}
// Draw the two possible overlay layers in reverse-priority order.
diff --git a/src/com/android/mail/bitmap/ImageAttachmentRequest.java b/src/com/android/mail/bitmap/ImageAttachmentRequest.java
index d60552b..6c58772 100644
--- a/src/com/android/mail/bitmap/ImageAttachmentRequest.java
+++ b/src/com/android/mail/bitmap/ImageAttachmentRequest.java
@@ -97,7 +97,7 @@
}
@Override
- public InputStream createInputStream() {
+ public InputStream createInputStream() throws IOException {
return null;
}
}
diff --git a/src/com/android/mail/ui/ThumbnailLoadTask.java b/src/com/android/mail/ui/ThumbnailLoadTask.java
index e4b8c20..c30864e 100644
--- a/src/com/android/mail/ui/ThumbnailLoadTask.java
+++ b/src/com/android/mail/ui/ThumbnailLoadTask.java
@@ -33,7 +33,6 @@
import com.android.mail.utils.LogTag;
import com.android.mail.utils.LogUtils;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -164,27 +163,11 @@
return 0;
}
- ByteArrayOutputStream out = null;
InputStream in = null;
try {
final ContentResolver resolver = mHolder.getResolver();
in = resolver.openInputStream(thumbnailUri);
- out = new ByteArrayOutputStream();
- final byte[] buffer = new byte[4096];
- int n = in.read(buffer);
- while (n >= 0) {
- out.write(buffer, 0, n);
- n = in.read(buffer);
- }
- in.close();
- in = null;
-
- if (isCancelled()) {
- return 0;
- }
-
- final byte[] bitmapBytes = out.toByteArray();
- return Exif.getOrientation(bitmapBytes);
+ return Exif.getOrientation(in, -1);
} catch (Throwable t) {
LogUtils.e(LOG_TAG, t, "Unable to get orientation of thumbnail %s", thumbnailUri);
} finally {
@@ -195,13 +178,6 @@
LogUtils.e(LOG_TAG, e, "error attemtping to close input stream");
}
}
- if (out != null) {
- try {
- out.close();
- } catch (IOException e) {
- LogUtils.e(LOG_TAG, e, "error attemtping to close output stream");
- }
- }
}
return 0;
diff --git a/src/com/android/mail/utils/RectUtils.java b/src/com/android/mail/utils/RectUtils.java
new file mode 100644
index 0000000..43d070d
--- /dev/null
+++ b/src/com/android/mail/utils/RectUtils.java
@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2013, Google Inc.
+ *
+ * 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.android.mail.utils;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+public class RectUtils {
+
+ /**
+ * Transform the upright full rectangle so that it bounds the original rotated image,
+ * given by the orientation. Transform the upright partial rectangle such that it would apply
+ * to the same region of the transformed full rectangle.
+ *
+ * The top-left of the transformed full rectangle will always be placed at (0, 0).
+ * @param orientation The exif orientation (0, 90, 180, 270) of the original image. The
+ * transformed full and partial rectangles will be in this orientation's
+ * coordinate space.
+ * @param fullRect The upright full rectangle. This rectangle will be modified.
+ * @param partialRect The upright partial rectangle. This rectangle will be modified.
+ */
+ public static void rotateRectForOrientation(final int orientation, final Rect fullRect,
+ final Rect partialRect) {
+ final Matrix matrix = new Matrix();
+ // Exif orientation specifies how the camera is rotated relative to the actual subject.
+ // First rotate in the opposite direction.
+ matrix.setRotate(-orientation);
+ final RectF fullRectF = new RectF(fullRect);
+ final RectF partialRectF = new RectF(partialRect);
+ matrix.mapRect(fullRectF);
+ matrix.mapRect(partialRectF);
+ // Then translate so that the upper left corner of the rotated full rect is at (0,0).
+ matrix.reset();
+ matrix.setTranslate(-fullRectF.left, -fullRectF.top);
+ matrix.mapRect(fullRectF);
+ matrix.mapRect(partialRectF);
+ // Orientation transformation is complete.
+ fullRect.set((int) fullRectF.left, (int) fullRectF.top, (int) fullRectF.right,
+ (int) fullRectF.bottom);
+ partialRect.set((int) partialRectF.left, (int) partialRectF.top, (int) partialRectF.right,
+ (int) partialRectF.bottom);
+ }
+
+ public static void rotateRect(final int degrees, final int px, final int py, final Rect rect) {
+ final RectF rectF = new RectF(rect);
+ final Matrix matrix = new Matrix();
+ matrix.setRotate(degrees, px, py);
+ matrix.mapRect(rectF);
+ rect.set((int) rectF.left, (int) rectF.top, (int) rectF.right, (int) rectF.bottom);
+ }
+}