| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| |
| package com.android.contacts.util; |
| |
| import android.content.ClipData; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.graphics.Bitmap; |
| import android.graphics.BitmapFactory; |
| import android.net.Uri; |
| import android.provider.MediaStore; |
| import androidx.core.content.FileProvider; |
| import android.util.Log; |
| import com.android.contacts.R; |
| |
| import com.google.common.io.Closeables; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.text.SimpleDateFormat; |
| import java.util.Date; |
| import java.util.Locale; |
| |
| /** |
| * Utilities related to loading/saving contact photos. |
| * |
| */ |
| public class ContactPhotoUtils { |
| private static final String TAG = "ContactPhotoUtils"; |
| |
| private static final String PHOTO_DATE_FORMAT = "'IMG'_yyyyMMdd_HHmmss"; |
| |
| /** |
| * Generate a new, unique file to be used as an out-of-band communication |
| * channel, since hi-res Bitmaps are too big to serialize into a Bundle. |
| * This file will be passed (as a uri) to other activities (such as the gallery/camera/ |
| * cropper/etc.), and read by us once they are finished writing it. |
| */ |
| public static Uri generateTempImageUri(Context context) { |
| final String fileProviderAuthority = context.getResources().getString( |
| R.string.photo_file_provider_authority); |
| return FileProvider.getUriForFile(context, fileProviderAuthority, |
| new File(pathForTempPhoto(context, generateTempPhotoFileName()))); |
| } |
| |
| public static Uri generateTempCroppedImageUri(Context context) { |
| final String fileProviderAuthority = context.getResources().getString( |
| R.string.photo_file_provider_authority); |
| return FileProvider.getUriForFile(context, fileProviderAuthority, |
| new File(pathForTempPhoto(context, generateTempCroppedPhotoFileName()))); |
| } |
| |
| private static String pathForTempPhoto(Context context, String fileName) { |
| final File dir = context.getCacheDir(); |
| dir.mkdirs(); |
| final File f = new File(dir, fileName); |
| return f.getAbsolutePath(); |
| } |
| |
| private static String generateTempPhotoFileName() { |
| final Date date = new Date(System.currentTimeMillis()); |
| SimpleDateFormat dateFormat = new SimpleDateFormat(PHOTO_DATE_FORMAT, Locale.US); |
| return "ContactPhoto-" + dateFormat.format(date) + ".jpg"; |
| } |
| |
| private static String generateTempCroppedPhotoFileName() { |
| final Date date = new Date(System.currentTimeMillis()); |
| SimpleDateFormat dateFormat = new SimpleDateFormat(PHOTO_DATE_FORMAT, Locale.US); |
| return "ContactPhoto-" + dateFormat.format(date) + "-cropped.jpg"; |
| } |
| |
| /** |
| * Given a uri pointing to a bitmap, reads it into a bitmap and returns it. |
| * @throws FileNotFoundException |
| */ |
| public static Bitmap getBitmapFromUri(Context context, Uri uri) throws FileNotFoundException { |
| final InputStream imageStream = context.getContentResolver().openInputStream(uri); |
| try { |
| return BitmapFactory.decodeStream(imageStream); |
| } finally { |
| Closeables.closeQuietly(imageStream); |
| } |
| } |
| |
| /** |
| * Creates a byte[] containing the PNG-compressed bitmap, or null if |
| * something goes wrong. |
| */ |
| public static byte[] compressBitmap(Bitmap bitmap) { |
| final int size = bitmap.getWidth() * bitmap.getHeight() * 4; |
| final ByteArrayOutputStream out = new ByteArrayOutputStream(size); |
| try { |
| bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); |
| out.flush(); |
| out.close(); |
| return out.toByteArray(); |
| } catch (IOException e) { |
| Log.w(TAG, "Unable to serialize photo: " + e.toString()); |
| return null; |
| } |
| } |
| |
| public static void addCropExtras(Intent intent, int photoSize) { |
| intent.putExtra("crop", "true"); |
| intent.putExtra("scale", true); |
| intent.putExtra("scaleUpIfNeeded", true); |
| intent.putExtra("aspectX", 1); |
| intent.putExtra("aspectY", 1); |
| intent.putExtra("outputX", photoSize); |
| intent.putExtra("outputY", photoSize); |
| } |
| |
| /** |
| * Adds common extras to gallery intents. |
| * |
| * @param intent The intent to add extras to. |
| * @param photoUri The uri of the file to save the image to. |
| */ |
| public static void addPhotoPickerExtras(Intent intent, Uri photoUri) { |
| intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri); |
| intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | |
| Intent.FLAG_GRANT_READ_URI_PERMISSION); |
| intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, photoUri)); |
| } |
| |
| /** |
| * Given an input photo stored in a uri, save it to a destination uri |
| */ |
| public static boolean savePhotoFromUriToUri(Context context, Uri inputUri, Uri outputUri, |
| boolean deleteAfterSave) { |
| if (inputUri == null || outputUri == null || isFilePathAndNotStorage(inputUri)) { |
| return false; |
| } |
| try (FileOutputStream outputStream = context.getContentResolver() |
| .openAssetFileDescriptor(outputUri, "rw").createOutputStream(); |
| InputStream inputStream = context.getContentResolver().openInputStream(inputUri)) { |
| |
| final byte[] buffer = new byte[16 * 1024]; |
| int length; |
| int totalLength = 0; |
| while ((length = inputStream.read(buffer)) > 0) { |
| outputStream.write(buffer, 0, length); |
| totalLength += length; |
| } |
| if (Log.isLoggable(TAG, Log.VERBOSE)) { |
| Log.v(TAG, "Wrote " + totalLength + " bytes for photo " + inputUri.toString()); |
| } |
| } catch (IOException | NullPointerException e) { |
| Log.e(TAG, "Failed to write photo: " + inputUri.toString() + " because: " + e); |
| return false; |
| } finally { |
| if (deleteAfterSave) { |
| context.getContentResolver().delete(inputUri, null, null); |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Returns {@code true} if the {@code inputUri} is a FILE scheme and it does not point to |
| * the storage directory. |
| */ |
| private static boolean isFilePathAndNotStorage(Uri inputUri) { |
| if (ContentResolver.SCHEME_FILE.equals(inputUri.getScheme())) { |
| try { |
| File file = new File(inputUri.getPath()).getCanonicalFile(); |
| return !file.getCanonicalPath().startsWith("/storage/"); |
| } catch (IOException e) { |
| return false; |
| } |
| } |
| return false; |
| } |
| } |