1e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus/*
2e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus * Copyright (C) 2012 The Android Open Source Project
3e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus *
4e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus * Licensed under the Apache License, Version 2.0 (the "License");
5e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus * you may not use this file except in compliance with the License.
6e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus * You may obtain a copy of the License at
7e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus *
8e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus *      http://www.apache.org/licenses/LICENSE-2.0
9e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus *
10e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus * Unless required by applicable law or agreed to in writing, software
11e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus * distributed under the License is distributed on an "AS IS" BASIS,
12e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus * See the License for the specific language governing permissions and
14e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus * limitations under the License.
15e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus */
16e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus
17e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus
18e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Garguspackage com.android.contacts.util;
19e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus
20637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Leeimport android.content.ClipData;
21ebc17929007ecf12f25b8e90023766e8a083d11eJosh Gargusimport android.content.Context;
2261414c2c4627ab588cc0f54eae5e0c5591f7d8d9Chiao Chengimport android.content.Intent;
23e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargusimport android.graphics.Bitmap;
24637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Leeimport android.graphics.BitmapFactory;
2561414c2c4627ab588cc0f54eae5e0c5591f7d8d9Chiao Chengimport android.net.Uri;
26e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargusimport android.os.Environment;
2761414c2c4627ab588cc0f54eae5e0c5591f7d8d9Chiao Chengimport android.provider.MediaStore;
28637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Leeimport android.support.v4.content.FileProvider;
29e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargusimport android.util.Log;
30e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus
31fc98ff91106de05bef5b59c3a21ed4ac724847fePaul Soulosimport com.android.contacts.R;
32637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Leeimport com.google.common.io.Closeables;
33637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee
34e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargusimport java.io.ByteArrayOutputStream;
35e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargusimport java.io.File;
36637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Leeimport java.io.FileNotFoundException;
37637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Leeimport java.io.FileOutputStream;
38e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargusimport java.io.IOException;
39637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Leeimport java.io.InputStream;
40e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargusimport java.text.SimpleDateFormat;
41e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargusimport java.util.Date;
429bcfc325333797ad968fbb42583a2006effdef4aJohan Redestigimport java.util.Locale;
43e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus
44e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus/**
45e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus * Utilities related to loading/saving contact photos.
46e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus *
47e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus */
48e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Garguspublic class ContactPhotoUtils {
49e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus    private static final String TAG = "ContactPhotoUtils";
50e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus
51e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus    private static final String PHOTO_DATE_FORMAT = "'IMG'_yyyyMMdd_HHmmss";
52e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus
53e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus    /**
54e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus     * Generate a new, unique file to be used as an out-of-band communication
55e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus     * channel, since hi-res Bitmaps are too big to serialize into a Bundle.
56637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee     * This file will be passed (as a uri) to other activities (such as the gallery/camera/
57637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee     *  cropper/etc.), and read by us once they are finished writing it.
58e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus     */
59637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee    public static Uri generateTempImageUri(Context context) {
60fc98ff91106de05bef5b59c3a21ed4ac724847fePaul Soulos        final String  fileProviderAuthority = context.getResources().getString(
61fc98ff91106de05bef5b59c3a21ed4ac724847fePaul Soulos                R.string.photo_file_provider_authority);
62fc98ff91106de05bef5b59c3a21ed4ac724847fePaul Soulos        return FileProvider.getUriForFile(context, fileProviderAuthority,
63637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee                new File(pathForTempPhoto(context, generateTempPhotoFileName())));
64e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus    }
65e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus
66637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee    public static Uri generateTempCroppedImageUri(Context context) {
67fc98ff91106de05bef5b59c3a21ed4ac724847fePaul Soulos        final String  fileProviderAuthority = context.getResources().getString(
68fc98ff91106de05bef5b59c3a21ed4ac724847fePaul Soulos                R.string.photo_file_provider_authority);
69fc98ff91106de05bef5b59c3a21ed4ac724847fePaul Soulos        return FileProvider.getUriForFile(context, fileProviderAuthority,
70637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee                new File(pathForTempPhoto(context, generateTempCroppedPhotoFileName())));
71ebc17929007ecf12f25b8e90023766e8a083d11eJosh Gargus    }
72ebc17929007ecf12f25b8e90023766e8a083d11eJosh Gargus
73637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee    private static String pathForTempPhoto(Context context, String fileName) {
74637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee        final File dir = context.getCacheDir();
75ebc17929007ecf12f25b8e90023766e8a083d11eJosh Gargus        dir.mkdirs();
76ebc17929007ecf12f25b8e90023766e8a083d11eJosh Gargus        final File f = new File(dir, fileName);
77ebc17929007ecf12f25b8e90023766e8a083d11eJosh Gargus        return f.getAbsolutePath();
78ebc17929007ecf12f25b8e90023766e8a083d11eJosh Gargus    }
79ebc17929007ecf12f25b8e90023766e8a083d11eJosh Gargus
80637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee    private static String generateTempPhotoFileName() {
81637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee        final Date date = new Date(System.currentTimeMillis());
829bcfc325333797ad968fbb42583a2006effdef4aJohan Redestig        SimpleDateFormat dateFormat = new SimpleDateFormat(PHOTO_DATE_FORMAT, Locale.US);
83ebc17929007ecf12f25b8e90023766e8a083d11eJosh Gargus        return "ContactPhoto-" + dateFormat.format(date) + ".jpg";
84e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus    }
85e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus
86637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee    private static String generateTempCroppedPhotoFileName() {
87637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee        final Date date = new Date(System.currentTimeMillis());
88637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee        SimpleDateFormat dateFormat = new SimpleDateFormat(PHOTO_DATE_FORMAT, Locale.US);
89637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee        return "ContactPhoto-" + dateFormat.format(date) + "-cropped.jpg";
90637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee    }
91637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee
92637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee    /**
93637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee     * Given a uri pointing to a bitmap, reads it into a bitmap and returns it.
94637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee     * @throws FileNotFoundException
95637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee     */
96637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee    public static Bitmap getBitmapFromUri(Context context, Uri uri) throws FileNotFoundException {
97637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee        final InputStream imageStream = context.getContentResolver().openInputStream(uri);
98637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee        try {
99637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee            return BitmapFactory.decodeStream(imageStream);
100637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee        } finally {
101637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee            Closeables.closeQuietly(imageStream);
102637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee        }
103637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee    }
104637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee
105e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus    /**
106e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus     * Creates a byte[] containing the PNG-compressed bitmap, or null if
107e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus     * something goes wrong.
108e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus     */
109e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus    public static byte[] compressBitmap(Bitmap bitmap) {
110e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus        final int size = bitmap.getWidth() * bitmap.getHeight() * 4;
111e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus        final ByteArrayOutputStream out = new ByteArrayOutputStream(size);
112e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus        try {
113e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
114e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            out.flush();
115e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            out.close();
116e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            return out.toByteArray();
117e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus        } catch (IOException e) {
118e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            Log.w(TAG, "Unable to serialize photo: " + e.toString());
119e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus            return null;
120e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus        }
121e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus    }
12261414c2c4627ab588cc0f54eae5e0c5591f7d8d9Chiao Cheng
123637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee    public static void addCropExtras(Intent intent, int photoSize) {
12461414c2c4627ab588cc0f54eae5e0c5591f7d8d9Chiao Cheng        intent.putExtra("crop", "true");
12561414c2c4627ab588cc0f54eae5e0c5591f7d8d9Chiao Cheng        intent.putExtra("scale", true);
12661414c2c4627ab588cc0f54eae5e0c5591f7d8d9Chiao Cheng        intent.putExtra("scaleUpIfNeeded", true);
12761414c2c4627ab588cc0f54eae5e0c5591f7d8d9Chiao Cheng        intent.putExtra("aspectX", 1);
12861414c2c4627ab588cc0f54eae5e0c5591f7d8d9Chiao Cheng        intent.putExtra("aspectY", 1);
12961414c2c4627ab588cc0f54eae5e0c5591f7d8d9Chiao Cheng        intent.putExtra("outputX", photoSize);
13061414c2c4627ab588cc0f54eae5e0c5591f7d8d9Chiao Cheng        intent.putExtra("outputY", photoSize);
131637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee    }
132637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee
133637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee    /**
134637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee     * Adds common extras to gallery intents.
135637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee     *
136637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee     * @param intent The intent to add extras to.
137637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee     * @param photoUri The uri of the file to save the image to.
138637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee     */
139637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee    public static void addPhotoPickerExtras(Intent intent, Uri photoUri) {
140637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee        intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
141637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee        intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
142637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee                Intent.FLAG_GRANT_READ_URI_PERMISSION);
143637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee        intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, photoUri));
144637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee    }
145637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee
146637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee    /**
147637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee     * Given an input photo stored in a uri, save it to a destination uri
148637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee     */
149637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee    public static boolean savePhotoFromUriToUri(Context context, Uri inputUri, Uri outputUri,
150637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee            boolean deleteAfterSave) {
151a9433713594c990cd3b0ac5a44e316e4b54992b0Jay Shrauner        if (inputUri == null || outputUri == null) {
152a9433713594c990cd3b0ac5a44e316e4b54992b0Jay Shrauner            return false;
153a9433713594c990cd3b0ac5a44e316e4b54992b0Jay Shrauner        }
154f43973fd13262553b497cd83f4ea9654a72ff22fPaul Duffin        try (FileOutputStream outputStream = context.getContentResolver()
155f43973fd13262553b497cd83f4ea9654a72ff22fPaul Duffin                 .openAssetFileDescriptor(outputUri, "rw").createOutputStream();
156f43973fd13262553b497cd83f4ea9654a72ff22fPaul Duffin             InputStream inputStream = context.getContentResolver().openInputStream(inputUri)) {
157637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee
158637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee            final byte[] buffer = new byte[16 * 1024];
159637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee            int length;
160637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee            int totalLength = 0;
161637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee            while ((length = inputStream.read(buffer)) > 0) {
162637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee                outputStream.write(buffer, 0, length);
163637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee                totalLength += length;
164637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee            }
165637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee            Log.v(TAG, "Wrote " + totalLength + " bytes for photo " + inputUri.toString());
166b716f50c6f30b347b403db8b39598c38aadd8c64Jay Shrauner        } catch (IOException | NullPointerException e) {
167637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee            Log.e(TAG, "Failed to write photo: " + inputUri.toString() + " because: " + e);
168637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee            return false;
169637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee        } finally {
170637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee            if (deleteAfterSave) {
171637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee                context.getContentResolver().delete(inputUri, null, null);
172637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee            }
173637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee        }
174637a38ec9de6b1f434d7a13105f2e747faae5107Yorke Lee        return true;
17561414c2c4627ab588cc0f54eae5e0c5591f7d8d9Chiao Cheng    }
176e5d3f897689c8ba0f275c7679c72eacb190ae9b8Josh Gargus}
177