// -*- Mode: java; c-basic-offset: 2; -*- // Copyright 2009-2011 Google, All Rights reserved // Copyright 2011-2012 MIT, All rights reserved // Released under the Apache License, Version 2.0 // http://www.apache.org/licenses/LICENSE-2.0 package com.google.appinventor.components.runtime; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; import java.util.Comparator; import android.app.Activity; import android.content.ContentResolver; import android.content.Intent; import android.net.Uri; import android.os.Environment; import android.provider.MediaStore; import android.util.Log; import android.webkit.MimeTypeMap; import com.google.appinventor.components.annotations.DesignerComponent; import com.google.appinventor.components.annotations.PropertyCategory; import com.google.appinventor.components.annotations.SimpleObject; import com.google.appinventor.components.annotations.SimpleProperty; import com.google.appinventor.components.annotations.UsesPermissions; import com.google.appinventor.components.common.ComponentCategory; import com.google.appinventor.components.common.YaVersion; import com.google.appinventor.components.runtime.util.ErrorMessages; import com.google.appinventor.components.runtime.util.MediaUtil; /** * Component enabling a user to select an image from the phone's gallery. * * @author halabelson@google.com (Hal Abelson) */ @DesignerComponent(version = YaVersion.IMAGEPICKER_COMPONENT_VERSION, description = "A special-purpose button. When the user taps an image picker, the " + "device's image gallery appears, and the user can choose an image. After an image is " + "picked, it is saved, and the <code>Selected</code> " + "property will be the name of the file where the image is stored. In order to not " + "fill up storage, a maximum of 10 images will be stored. Picking more images " + "will delete previous images, in order from oldest to newest.", category = ComponentCategory.MEDIA) @UsesPermissions(permissionNames = "android.permission.WRITE_EXTERNAL_STORAGE") @SimpleObject public class ImagePicker extends Picker implements ActivityResultListener { private static final String LOG_TAG = "ImagePicker"; // directory on external storage for storing the files for the saved images private static final String imagePickerDirectoryName = "/Pictures/_app_inventor_image_picker"; // prefix for image file names private static final String FILE_PREFIX = "picked_image"; // max number of files to save in image directory private static int maxSavedFiles = 10; // The media path (URI) for the selected image file created by MediaUtil private String selectionURI; // The path to the saved image private String selectionSavedImage = ""; /** * Create a new ImagePicker component. * * @param container the parent container. */ public ImagePicker(ComponentContainer container) { super(container); } /** * Path to the file containing the image that was selected. */ @SimpleProperty(description = "Path to the file containing the image that was selected.", category = PropertyCategory.BEHAVIOR) public String Selection() { return selectionSavedImage; } @Override protected Intent getIntent() { return new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI); } /** * Callback method to get the result returned by the image picker activity * * @param requestCode a code identifying the request. * @param resultCode a code specifying success or failure of the activity * @param data the returned data, in this case an Intent whose data field * contains the image's content URI. */ public void resultReturned(int requestCode, int resultCode, Intent data) { if (requestCode == this.requestCode && resultCode == Activity.RESULT_OK) { Uri selectedImage = data.getData(); selectionURI = selectedImage.toString(); Log.i(LOG_TAG, "selectionURI = " + selectionURI); // get the file type extension from the intent data Uri ContentResolver cR = container.$context().getContentResolver(); MimeTypeMap mime = MimeTypeMap.getSingleton(); String extension = "." + mime.getExtensionFromMimeType(cR.getType(selectedImage)); Log.i(LOG_TAG, "extension = " + extension); // save the image to a temp file in external storage, using a name // that includes the extension saveSelectedImageToExternalStorage(extension); AfterPicking(); } } private void saveSelectedImageToExternalStorage(String extension) { // clear imageFile for new save attempt // This will be the stored picture selectionSavedImage = ""; // create a temp file for holding the image that was picked // This is not the external stored file: This is in the internal directory used by MediaUtil File tempFile = null; // copy the picture at the image URI to the temp file try { tempFile = MediaUtil.copyMediaToTempFile(container.$form(), selectionURI); } catch (IOException e) { Log.i(LOG_TAG, "copyMediaToTempFile failed: " + e.getMessage()); container.$form().dispatchErrorOccurredEvent(this, "ImagePicker", ErrorMessages.ERROR_CANNOT_COPY_MEDIA, e.getMessage()); return; } // copy the temp file to external storage Log.i(LOG_TAG, "temp file path is: " + tempFile.getPath()); // Copy file will signal a screen error if the copy fails. copyToExternalStorageAndDeleteSource(tempFile, extension); } private void copyToExternalStorageAndDeleteSource(File source, String extension) { File dest = null; InputStream inStream = null; OutputStream outStream = null; String fullDirname = Environment.getExternalStorageDirectory() + imagePickerDirectoryName; File destDirectory = new File(fullDirname); try { destDirectory.mkdirs(); dest = File.createTempFile (FILE_PREFIX, extension, destDirectory); selectionSavedImage = dest.getPath(); // Uncomment this to delete imageFile when the application stops // dest.deleteOnExit(); Log.i(LOG_TAG, "saved file path is: " + selectionSavedImage); inStream = new FileInputStream(source); outStream = new FileOutputStream(dest); byte[] buffer = new byte[1024]; int length; // copy the file content in bytes while ((length = inStream.read(buffer)) > 0){ outStream.write(buffer, 0, length); } inStream.close(); outStream.close(); Log.i(LOG_TAG, "Image was copied to " + selectionSavedImage); // this can be uncommented to show the alert, but the alert // is pretty annoying // new (container.$form()).ShowAlert("Image was copied to " + selectedImage); } catch(IOException e) { String err = "destination is " + selectionSavedImage + ": " + "error is " + e.getMessage(); Log.i(LOG_TAG, "copyFile failed. " + err); container.$form().dispatchErrorOccurredEvent(this, "SaveImage", ErrorMessages.ERROR_CANNOT_SAVE_IMAGE, err); selectionSavedImage = ""; dest.delete(); } // clean up the temp file. This isn't critical because MudiaUtil.copyMediaToTempFile // marks this with deleteOnExit, but it's nice to clean up here. source.delete(); trimDirectory(maxSavedFiles, destDirectory); } // keep only the last N files, where N = maxSavedFiles private void trimDirectory(int maxSavedFiles, File directory) { File[] files = directory.listFiles(); Arrays.sort(files, new Comparator<File>(){ public int compare(File f1, File f2) { return Long.valueOf(f1.lastModified()).compareTo(f2.lastModified()); } }); int excess = files.length - maxSavedFiles; for (int i = 0; i < excess; i++) { files[i].delete(); } } }