1 /* 2 * Copyright (C) 2015 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.android.messaging.ui.mediapicker; 18 19 import android.content.Context; 20 import android.graphics.Bitmap; 21 import android.graphics.BitmapFactory; 22 import android.graphics.Canvas; 23 import android.graphics.Matrix; 24 import android.net.Uri; 25 26 import com.android.messaging.datamodel.MediaScratchFileProvider; 27 import com.android.messaging.util.Assert; 28 import com.android.messaging.util.ContentType; 29 import com.android.messaging.util.LogUtil; 30 import com.android.messaging.util.SafeAsyncTask; 31 import com.android.messaging.util.exif.ExifInterface; 32 import com.android.messaging.util.exif.ExifTag; 33 34 import java.io.IOException; 35 import java.io.OutputStream; 36 37 public class ImagePersistTask extends SafeAsyncTask<Void, Void, Void> { 38 private static final String JPEG_EXTENSION = "jpg"; 39 private static final String TAG = LogUtil.BUGLE_TAG; 40 41 private int mWidth; 42 private int mHeight; 43 private final float mHeightPercent; 44 private final byte[] mBytes; 45 private final Context mContext; 46 private final CameraManager.MediaCallback mCallback; 47 private Uri mOutputUri; 48 private Exception mException; 49 50 public ImagePersistTask( 51 final int width, 52 final int height, 53 final float heightPercent, 54 final byte[] bytes, 55 final Context context, 56 final CameraManager.MediaCallback callback) { 57 Assert.isTrue(heightPercent >= 0 && heightPercent <= 1); 58 Assert.notNull(bytes); 59 Assert.notNull(context); 60 Assert.notNull(callback); 61 mWidth = width; 62 mHeight = height; 63 mHeightPercent = heightPercent; 64 mBytes = bytes; 65 mContext = context; 66 mCallback = callback; 67 // TODO: We probably want to store directly in MMS storage to prevent this 68 // intermediate step 69 mOutputUri = MediaScratchFileProvider.buildMediaScratchSpaceUri(JPEG_EXTENSION); 70 } 71 72 @Override 73 protected Void doInBackgroundTimed(final Void... params) { 74 OutputStream outputStream = null; 75 Bitmap bitmap = null; 76 Bitmap clippedBitmap = null; 77 try { 78 outputStream = 79 mContext.getContentResolver().openOutputStream(mOutputUri); 80 if (mHeightPercent != 1.0f) { 81 int orientation = android.media.ExifInterface.ORIENTATION_UNDEFINED; 82 final ExifInterface exifInterface = new ExifInterface(); 83 try { 84 exifInterface.readExif(mBytes); 85 final Integer orientationValue = 86 exifInterface.getTagIntValue(ExifInterface.TAG_ORIENTATION); 87 if (orientationValue != null) { 88 orientation = orientationValue.intValue(); 89 } 90 // The thumbnail is of the full image, but we're cropping it, so just clear 91 // the thumbnail 92 exifInterface.setCompressedThumbnail((byte[]) null); 93 } catch (IOException e) { 94 // Couldn't get exif tags, not the end of the world 95 } 96 bitmap = BitmapFactory.decodeByteArray(mBytes, 0, mBytes.length); 97 final int clippedWidth; 98 final int clippedHeight; 99 if (ExifInterface.getOrientationParams(orientation).invertDimensions) { 100 Assert.equals(mWidth, bitmap.getHeight()); 101 Assert.equals(mHeight, bitmap.getWidth()); 102 clippedWidth = (int) (mHeight * mHeightPercent); 103 clippedHeight = mWidth; 104 } else { 105 Assert.equals(mWidth, bitmap.getWidth()); 106 Assert.equals(mHeight, bitmap.getHeight()); 107 clippedWidth = mWidth; 108 clippedHeight = (int) (mHeight * mHeightPercent); 109 } 110 final int offsetTop = (bitmap.getHeight() - clippedHeight) / 2; 111 final int offsetLeft = (bitmap.getWidth() - clippedWidth) / 2; 112 mWidth = clippedWidth; 113 mHeight = clippedHeight; 114 clippedBitmap = Bitmap.createBitmap(clippedWidth, clippedHeight, 115 Bitmap.Config.ARGB_8888); 116 clippedBitmap.setDensity(bitmap.getDensity()); 117 final Canvas clippedBitmapCanvas = new Canvas(clippedBitmap); 118 final Matrix matrix = new Matrix(); 119 matrix.postTranslate(-offsetLeft, -offsetTop); 120 clippedBitmapCanvas.drawBitmap(bitmap, matrix, null /* paint */); 121 clippedBitmapCanvas.save(); 122 // EXIF data can take a big chunk of the file size and is often cleared by the 123 // carrier, only store orientation since that's critical 124 ExifTag orientationTag = exifInterface.getTag(ExifInterface.TAG_ORIENTATION); 125 exifInterface.clearExif(); 126 exifInterface.setTag(orientationTag); 127 exifInterface.writeExif(clippedBitmap, outputStream); 128 } else { 129 outputStream.write(mBytes); 130 } 131 } catch (final IOException e) { 132 mOutputUri = null; 133 mException = e; 134 LogUtil.e(TAG, "Unable to persist image to temp storage " + e); 135 } finally { 136 if (bitmap != null) { 137 bitmap.recycle(); 138 } 139 140 if (clippedBitmap != null) { 141 clippedBitmap.recycle(); 142 } 143 144 if (outputStream != null) { 145 try { 146 outputStream.flush(); 147 } catch (final IOException e) { 148 mOutputUri = null; 149 mException = e; 150 LogUtil.e(TAG, "error trying to flush and close the outputStream" + e); 151 } finally { 152 try { 153 outputStream.close(); 154 } catch (final IOException e) { 155 // Do nothing. 156 } 157 } 158 } 159 } 160 return null; 161 } 162 163 @Override 164 protected void onPostExecute(final Void aVoid) { 165 if (mOutputUri != null) { 166 mCallback.onMediaReady(mOutputUri, ContentType.IMAGE_JPEG, mWidth, mHeight); 167 } else { 168 Assert.notNull(mException); 169 mCallback.onMediaFailed(mException); 170 } 171 } 172 } 173