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
17package com.android.dialer.callcomposer.camera;
18
19import android.annotation.TargetApi;
20import android.content.Context;
21import android.graphics.Bitmap;
22import android.graphics.BitmapFactory;
23import android.graphics.Canvas;
24import android.graphics.Matrix;
25import android.net.Uri;
26import android.os.Build.VERSION_CODES;
27import android.support.v4.content.FileProvider;
28import com.android.dialer.callcomposer.camera.exif.ExifInterface;
29import com.android.dialer.callcomposer.camera.exif.ExifTag;
30import com.android.dialer.callcomposer.util.BitmapResizer;
31import com.android.dialer.common.Assert;
32import com.android.dialer.common.concurrent.FallibleAsyncTask;
33import com.android.dialer.constants.Constants;
34import com.android.dialer.util.DialerUtils;
35import java.io.File;
36import java.io.FileOutputStream;
37import java.io.IOException;
38import java.io.OutputStream;
39
40/** Persisting image routine. */
41@TargetApi(VERSION_CODES.M)
42public 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
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
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
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
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