1/*
2 * Copyright (C) 2012 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
18package com.android.contacts.util;
19
20import android.content.ClipData;
21import android.content.Context;
22import android.content.Intent;
23import android.graphics.Bitmap;
24import android.graphics.BitmapFactory;
25import android.net.Uri;
26import android.os.Environment;
27import android.provider.MediaStore;
28import android.support.v4.content.FileProvider;
29import android.util.Log;
30
31import com.android.contacts.R;
32import com.google.common.io.Closeables;
33
34import java.io.ByteArrayOutputStream;
35import java.io.File;
36import java.io.FileNotFoundException;
37import java.io.FileOutputStream;
38import java.io.IOException;
39import java.io.InputStream;
40import java.text.SimpleDateFormat;
41import java.util.Date;
42import java.util.Locale;
43
44/**
45 * Utilities related to loading/saving contact photos.
46 *
47 */
48public class ContactPhotoUtils {
49    private static final String TAG = "ContactPhotoUtils";
50
51    private static final String PHOTO_DATE_FORMAT = "'IMG'_yyyyMMdd_HHmmss";
52
53    /**
54     * Generate a new, unique file to be used as an out-of-band communication
55     * channel, since hi-res Bitmaps are too big to serialize into a Bundle.
56     * This file will be passed (as a uri) to other activities (such as the gallery/camera/
57     *  cropper/etc.), and read by us once they are finished writing it.
58     */
59    public static Uri generateTempImageUri(Context context) {
60        final String  fileProviderAuthority = context.getResources().getString(
61                R.string.photo_file_provider_authority);
62        return FileProvider.getUriForFile(context, fileProviderAuthority,
63                new File(pathForTempPhoto(context, generateTempPhotoFileName())));
64    }
65
66    public static Uri generateTempCroppedImageUri(Context context) {
67        final String  fileProviderAuthority = context.getResources().getString(
68                R.string.photo_file_provider_authority);
69        return FileProvider.getUriForFile(context, fileProviderAuthority,
70                new File(pathForTempPhoto(context, generateTempCroppedPhotoFileName())));
71    }
72
73    private static String pathForTempPhoto(Context context, String fileName) {
74        final File dir = context.getCacheDir();
75        dir.mkdirs();
76        final File f = new File(dir, fileName);
77        return f.getAbsolutePath();
78    }
79
80    private static String generateTempPhotoFileName() {
81        final Date date = new Date(System.currentTimeMillis());
82        SimpleDateFormat dateFormat = new SimpleDateFormat(PHOTO_DATE_FORMAT, Locale.US);
83        return "ContactPhoto-" + dateFormat.format(date) + ".jpg";
84    }
85
86    private static String generateTempCroppedPhotoFileName() {
87        final Date date = new Date(System.currentTimeMillis());
88        SimpleDateFormat dateFormat = new SimpleDateFormat(PHOTO_DATE_FORMAT, Locale.US);
89        return "ContactPhoto-" + dateFormat.format(date) + "-cropped.jpg";
90    }
91
92    /**
93     * Given a uri pointing to a bitmap, reads it into a bitmap and returns it.
94     * @throws FileNotFoundException
95     */
96    public static Bitmap getBitmapFromUri(Context context, Uri uri) throws FileNotFoundException {
97        final InputStream imageStream = context.getContentResolver().openInputStream(uri);
98        try {
99            return BitmapFactory.decodeStream(imageStream);
100        } finally {
101            Closeables.closeQuietly(imageStream);
102        }
103    }
104
105    /**
106     * Creates a byte[] containing the PNG-compressed bitmap, or null if
107     * something goes wrong.
108     */
109    public static byte[] compressBitmap(Bitmap bitmap) {
110        final int size = bitmap.getWidth() * bitmap.getHeight() * 4;
111        final ByteArrayOutputStream out = new ByteArrayOutputStream(size);
112        try {
113            bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
114            out.flush();
115            out.close();
116            return out.toByteArray();
117        } catch (IOException e) {
118            Log.w(TAG, "Unable to serialize photo: " + e.toString());
119            return null;
120        }
121    }
122
123    public static void addCropExtras(Intent intent, int photoSize) {
124        intent.putExtra("crop", "true");
125        intent.putExtra("scale", true);
126        intent.putExtra("scaleUpIfNeeded", true);
127        intent.putExtra("aspectX", 1);
128        intent.putExtra("aspectY", 1);
129        intent.putExtra("outputX", photoSize);
130        intent.putExtra("outputY", photoSize);
131    }
132
133    /**
134     * Adds common extras to gallery intents.
135     *
136     * @param intent The intent to add extras to.
137     * @param photoUri The uri of the file to save the image to.
138     */
139    public static void addPhotoPickerExtras(Intent intent, Uri photoUri) {
140        intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
141        intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
142                Intent.FLAG_GRANT_READ_URI_PERMISSION);
143        intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, photoUri));
144    }
145
146    /**
147     * Given an input photo stored in a uri, save it to a destination uri
148     */
149    public static boolean savePhotoFromUriToUri(Context context, Uri inputUri, Uri outputUri,
150            boolean deleteAfterSave) {
151        if (inputUri == null || outputUri == null) {
152            return false;
153        }
154        try (FileOutputStream outputStream = context.getContentResolver()
155                 .openAssetFileDescriptor(outputUri, "rw").createOutputStream();
156             InputStream inputStream = context.getContentResolver().openInputStream(inputUri)) {
157
158            final byte[] buffer = new byte[16 * 1024];
159            int length;
160            int totalLength = 0;
161            while ((length = inputStream.read(buffer)) > 0) {
162                outputStream.write(buffer, 0, length);
163                totalLength += length;
164            }
165            Log.v(TAG, "Wrote " + totalLength + " bytes for photo " + inputUri.toString());
166        } catch (IOException | NullPointerException e) {
167            Log.e(TAG, "Failed to write photo: " + inputUri.toString() + " because: " + e);
168            return false;
169        } finally {
170            if (deleteAfterSave) {
171                context.getContentResolver().delete(inputUri, null, null);
172            }
173        }
174        return true;
175    }
176}
177