1 /* 2 * Copyright (C) 2016 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.dialer.callcomposer.camera; 18 19 import android.annotation.TargetApi; 20 import android.content.Context; 21 import android.graphics.Bitmap; 22 import android.graphics.BitmapFactory; 23 import android.graphics.Canvas; 24 import android.graphics.Matrix; 25 import android.net.Uri; 26 import android.os.Build.VERSION_CODES; 27 import android.support.v4.content.FileProvider; 28 import com.android.dialer.callcomposer.camera.exif.ExifInterface; 29 import com.android.dialer.callcomposer.camera.exif.ExifTag; 30 import com.android.dialer.callcomposer.util.BitmapResizer; 31 import com.android.dialer.common.Assert; 32 import com.android.dialer.common.concurrent.FallibleAsyncTask; 33 import com.android.dialer.constants.Constants; 34 import com.android.dialer.util.DialerUtils; 35 import java.io.File; 36 import java.io.FileOutputStream; 37 import java.io.IOException; 38 import java.io.OutputStream; 39 40 /** Persisting image routine. */ 41 @TargetApi(VERSION_CODES.M) 42 public class ImagePersistTask extends FallibleAsyncTask<Void, Void, Uri> { 43 private int mWidth; 44 private int mHeight; 45 private final float mHeightPercent; 46 private final byte[] mBytes; 47 private final Context mContext; 48 private final CameraManager.MediaCallback mCallback; 49 ImagePersistTask( final int width, final int height, final float heightPercent, final byte[] bytes, final Context context, final CameraManager.MediaCallback callback)50 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.checkArgument(heightPercent >= 0 && heightPercent <= 1); 58 Assert.isNotNull(bytes); 59 Assert.isNotNull(context); 60 Assert.isNotNull(callback); 61 mWidth = width; 62 mHeight = height; 63 mHeightPercent = heightPercent; 64 mBytes = bytes; 65 mContext = context; 66 mCallback = callback; 67 } 68 69 @Override doInBackgroundFallible(final Void... params)70 protected Uri doInBackgroundFallible(final Void... params) throws Exception { 71 File outputFile = DialerUtils.createShareableFile(mContext); 72 73 try (OutputStream outputStream = new FileOutputStream(outputFile)) { 74 if (mHeightPercent != 1.0f) { 75 writeClippedBitmap(outputStream); 76 } else { 77 Bitmap bitmap = BitmapFactory.decodeByteArray(mBytes, 0, mBytes.length); 78 bitmap = BitmapResizer.resizeForEnrichedCalling(bitmap); 79 bitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream); 80 } 81 } 82 83 return FileProvider.getUriForFile( 84 mContext, Constants.get().getFileProviderAuthority(), outputFile); 85 } 86 87 @Override onPostExecute(FallibleTaskResult<Uri> result)88 protected void onPostExecute(FallibleTaskResult<Uri> result) { 89 if (result.isFailure()) { 90 mCallback.onMediaFailed(new Exception("Persisting image failed", result.getThrowable())); 91 } else { 92 mCallback.onMediaReady(result.getResult(), "image/jpeg", mWidth, mHeight); 93 } 94 } 95 writeClippedBitmap(OutputStream outputStream)96 private void writeClippedBitmap(OutputStream outputStream) throws IOException { 97 int orientation = android.media.ExifInterface.ORIENTATION_UNDEFINED; 98 final ExifInterface exifInterface = new ExifInterface(); 99 try { 100 exifInterface.readExif(mBytes); 101 final Integer orientationValue = exifInterface.getTagIntValue(ExifInterface.TAG_ORIENTATION); 102 if (orientationValue != null) { 103 orientation = orientationValue.intValue(); 104 } 105 } catch (final IOException e) { 106 // Couldn't get exif tags, not the end of the world 107 } 108 Bitmap bitmap = BitmapFactory.decodeByteArray(mBytes, 0, mBytes.length); 109 final int clippedWidth; 110 final int clippedHeight; 111 if (ExifInterface.getOrientationParams(orientation).invertDimensions) { 112 Assert.checkState(mWidth == bitmap.getHeight()); 113 Assert.checkState(mHeight == bitmap.getWidth()); 114 clippedWidth = (int) (mHeight * mHeightPercent); 115 clippedHeight = mWidth; 116 } else { 117 Assert.checkState(mWidth == bitmap.getWidth()); 118 Assert.checkState(mHeight == bitmap.getHeight()); 119 clippedWidth = mWidth; 120 clippedHeight = (int) (mHeight * mHeightPercent); 121 } 122 final int offsetTop = (bitmap.getHeight() - clippedHeight) / 2; 123 final int offsetLeft = (bitmap.getWidth() - clippedWidth) / 2; 124 mWidth = clippedWidth; 125 mHeight = clippedHeight; 126 Bitmap clippedBitmap = 127 Bitmap.createBitmap(clippedWidth, clippedHeight, Bitmap.Config.ARGB_8888); 128 clippedBitmap.setDensity(bitmap.getDensity()); 129 final Canvas clippedBitmapCanvas = new Canvas(clippedBitmap); 130 final Matrix matrix = new Matrix(); 131 matrix.postTranslate(-offsetLeft, -offsetTop); 132 clippedBitmapCanvas.drawBitmap(bitmap, matrix, null /* paint */); 133 clippedBitmapCanvas.save(); 134 clippedBitmap = BitmapResizer.resizeForEnrichedCalling(clippedBitmap); 135 // EXIF data can take a big chunk of the file size and is often cleared by the 136 // carrier, only store orientation since that's critical 137 final ExifTag orientationTag = exifInterface.getTag(ExifInterface.TAG_ORIENTATION); 138 exifInterface.clearExif(); 139 exifInterface.setTag(orientationTag); 140 exifInterface.writeExif(clippedBitmap, outputStream); 141 142 clippedBitmap.recycle(); 143 bitmap.recycle(); 144 } 145 } 146