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