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.content.Context;
20 import android.graphics.Bitmap;
21 import android.graphics.BitmapFactory;
22 import android.net.Uri;
23 import android.support.annotation.NonNull;
24 import android.support.v4.content.FileProvider;
25 import com.android.dialer.callcomposer.camera.ImagePersistWorker.Result;
26 import com.android.dialer.callcomposer.camera.exif.ExifInterface;
27 import com.android.dialer.callcomposer.util.BitmapResizer;
28 import com.android.dialer.common.Assert;
29 import com.android.dialer.common.concurrent.DialerExecutor.Worker;
30 import com.android.dialer.constants.Constants;
31 import com.android.dialer.util.DialerUtils;
32 import com.google.auto.value.AutoValue;
33 import java.io.File;
34 import java.io.FileOutputStream;
35 import java.io.IOException;
36 import java.io.OutputStream;
37 
38 /** Persisting image routine. */
39 public class ImagePersistWorker implements Worker<Void, Result> {
40   private int width;
41   private int height;
42   private final float heightPercent;
43   private final byte[] bytes;
44   private final Context context;
45 
46   @AutoValue
47   abstract static class Result {
48 
builder()49     public static Builder builder() {
50       return new AutoValue_ImagePersistWorker_Result.Builder();
51     }
52 
53     @NonNull
getUri()54     abstract Uri getUri();
55 
getWidth()56     abstract int getWidth();
57 
getHeight()58     abstract int getHeight();
59 
60     @AutoValue.Builder
61     abstract static class Builder {
setUri(@onNull Uri uri)62       abstract Builder setUri(@NonNull Uri uri);
63 
setWidth(int width)64       abstract Builder setWidth(int width);
65 
setHeight(int height)66       abstract Builder setHeight(int height);
67 
build()68       abstract Result build();
69     }
70   }
71 
ImagePersistWorker( final int width, final int height, final float heightPercent, final byte[] bytes, final Context context)72   ImagePersistWorker(
73       final int width,
74       final int height,
75       final float heightPercent,
76       final byte[] bytes,
77       final Context context) {
78     Assert.checkArgument(heightPercent >= 0 && heightPercent <= 1);
79     Assert.isNotNull(bytes);
80     Assert.isNotNull(context);
81     this.width = width;
82     this.height = height;
83     this.heightPercent = heightPercent;
84     this.bytes = bytes;
85     this.context = context;
86   }
87 
88   @Override
doInBackground(Void unused)89   public Result doInBackground(Void unused) throws Exception {
90     File outputFile = DialerUtils.createShareableFile(context);
91 
92     try (OutputStream outputStream = new FileOutputStream(outputFile)) {
93       writeClippedBitmap(outputStream);
94     }
95 
96     return Result.builder()
97         .setUri(
98             FileProvider.getUriForFile(
99                 context, Constants.get().getFileProviderAuthority(), outputFile))
100         .setWidth(width)
101         .setHeight(height)
102         .build();
103   }
104 
writeClippedBitmap(OutputStream outputStream)105   private void writeClippedBitmap(OutputStream outputStream) throws IOException {
106     int orientation = android.media.ExifInterface.ORIENTATION_UNDEFINED;
107     final ExifInterface exifInterface = new ExifInterface();
108     try {
109       exifInterface.readExif(bytes);
110       final Integer orientationValue = exifInterface.getTagIntValue(ExifInterface.TAG_ORIENTATION);
111       if (orientationValue != null) {
112         orientation = orientationValue.intValue();
113       }
114     } catch (final IOException e) {
115       // Couldn't get exif tags, not the end of the world
116     }
117 
118     ExifInterface.OrientationParams params = ExifInterface.getOrientationParams(orientation);
119     Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
120     final int clippedWidth;
121     final int clippedHeight;
122     if (params.invertDimensions) {
123       Assert.checkState(width == bitmap.getHeight());
124       Assert.checkState(height == bitmap.getWidth());
125       clippedWidth = (int) (height * heightPercent);
126       clippedHeight = width;
127     } else {
128       Assert.checkState(width == bitmap.getWidth());
129       Assert.checkState(height == bitmap.getHeight());
130       clippedWidth = width;
131       clippedHeight = (int) (height * heightPercent);
132     }
133 
134     int offsetTop = (bitmap.getHeight() - clippedHeight) / 2;
135     int offsetLeft = (bitmap.getWidth() - clippedWidth) / 2;
136     width = clippedWidth;
137     height = clippedHeight;
138 
139     Bitmap clippedBitmap =
140         Bitmap.createBitmap(bitmap, offsetLeft, offsetTop, clippedWidth, clippedHeight);
141     clippedBitmap = BitmapResizer.resizeForEnrichedCalling(clippedBitmap, params.rotation);
142     // EXIF data can take a big chunk of the file size and we've already manually rotated our image,
143     // so remove all of the exif data.
144     exifInterface.clearExif();
145     exifInterface.writeExif(clippedBitmap, outputStream);
146 
147     clippedBitmap.recycle();
148     bitmap.recycle();
149   }
150 }
151