/** * */ package org.awesomeapp.messenger.util; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.net.Uri; import android.os.Environment; import android.support.media.ExifInterface; import android.text.TextUtils; import android.util.Log; import org.apache.commons.io.IOUtils; import org.awesomeapp.messenger.ImApp; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.Date; import java.util.UUID; import info.guardianproject.iocipher.File; import info.guardianproject.iocipher.FileInputStream; import info.guardianproject.iocipher.FileOutputStream; import info.guardianproject.iocipher.VirtualFileSystem; /** * Copyright (C) 2014 Guardian Project. All rights reserved. * * @author liorsaar * */ public class SecureMediaStore { public static final String TAG = SecureMediaStore.class.getName(); private static String dbFilePath; private static final String BLOB_NAME = "media.db"; public static final int DEFAULT_IMAGE_WIDTH = 1080; public static void unmount() { VirtualFileSystem.get().unmount(); } public static void list(String parent) { File file = new File(parent); String[] list = file.list(); Log.d(TAG, "Dir=" + file.isDirectory() + ";" + file.getAbsolutePath() + ";last=" + new Date(file.lastModified())); for (int i = 0 ; i < list.length ; i++) { File fileChild = new File(parent,list[i]); if (fileChild.isDirectory()) { list(fileChild.getAbsolutePath()); } else { Log.d(TAG, "Dir=" + fileChild.isDirectory() + ";" + fileChild.getAbsolutePath()+ ";last=" + new Date(fileChild.lastModified())); } } } public static void deleteSession( String sessionId ) throws IOException { String dirName = "/" + sessionId; File file = new File(dirName); // if the session doesnt have any ul/dl files - bail if (!file.exists()) { return; } // delete recursive delete( dirName ); } private static void delete(String parentName) throws IOException { File parent = new File(parentName); // if a file or an empty directory - delete it if (!parent.isDirectory() || parent.list().length == 0 ) { // Log.e(TAG, "delete:" + parent ); if (!parent.delete()) { throw new IOException("Error deleting " + parent); } return; } // directory - recurse String[] list = parent.list(); for (int i = 0 ; i < list.length ; i++) { String childName = parentName + "/" + list[i]; delete( childName ); } delete( parentName ); } private static final String VFS_SCHEME = "vfs"; private static final String CONTENT_SCHEME = "content"; public static Uri vfsUri(String filename) { return Uri.parse(VFS_SCHEME + ":" + filename); } public static boolean isVfsUri(Uri uri) { return TextUtils.equals(VFS_SCHEME, uri.getScheme()); } public static boolean isContentUri(Uri uri) { return TextUtils.equals(CONTENT_SCHEME, uri.getScheme()); } public static boolean isContentUri(String uriString) { if (TextUtils.isEmpty(uriString)) return false; else return uriString.startsWith(CONTENT_SCHEME + ":/"); } public static boolean isVfsUri(String uriString) { if (TextUtils.isEmpty(uriString)) return false; else return uriString.startsWith(VFS_SCHEME + ":/"); } public static Bitmap getThumbnailVfs(Uri uri, int thumbnailSize) { if (!VirtualFileSystem.get().isMounted()) return null; File image = new File(uri.getPath()); BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; options.inInputShareable = true; options.inPurgeable = true; try { FileInputStream fis = new FileInputStream(new File(image.getPath())); BitmapFactory.decodeStream(fis, null, options); } catch (Exception e) { LogCleaner.warn(ImApp.LOG_TAG,"unable to read vfs thumbnail" + e.toString()); return null; } if ((options.outWidth == -1) || (options.outHeight == -1)) return null; int originalSize = (options.outHeight > options.outWidth) ? options.outHeight : options.outWidth; BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inSampleSize = originalSize / thumbnailSize; try { FileInputStream fis = new FileInputStream(new File(image.getPath())); Bitmap scaledBitmap = BitmapFactory.decodeStream(fis, null, opts); return scaledBitmap; } catch (FileNotFoundException e) { LogCleaner.warn(ImApp.LOG_TAG, "can't find IOcipher file: " + image.getPath()); return null; } catch (OutOfMemoryError oe) { LogCleaner.error(ImApp.LOG_TAG, "out of memory loading thumbnail: " + image.getPath(), oe); return null; } } /** * Careful! All of the {@code File}s in this method are {@link java.io.File} * not {@link info.guardianproject.iocipher.File}s * * @param context * @param key * @throws IllegalArgumentException */ public static void init(Context context, byte[] key) throws IllegalArgumentException { // there is only one VFS, so if its already mounted, nothing to do VirtualFileSystem vfs = VirtualFileSystem.get(); if (vfs.isMounted()) { Log.w(TAG, "VFS " + vfs.getContainerPath() + " is already mounted, so unmount()"); try { vfs.unmount(); } catch (Exception e) { Log.w(TAG, "VFS " + vfs.getContainerPath() + " issues with unmounting: " + e.getMessage()); } } Log.w(TAG,"Mounting VFS: " + vfs.getContainerPath()); dbFilePath = getInternalDbFilePath(context); if (!new java.io.File(dbFilePath).exists()) { vfs.createNewContainer(dbFilePath, key); } try { vfs.mount(dbFilePath, key); // list("/"); } catch (Exception e) { Log.w(TAG, "VFS " + vfs.getContainerPath() + " issues with mounting: " + e.getMessage()); } } public static boolean isMounted () { return VirtualFileSystem.get().isMounted(); } /** * get the internal storage path for the chat media file storage file. */ public static String getInternalDbFilePath(Context c) { return c.getFilesDir() + "/" + BLOB_NAME; } /** * Copy device content into vfs. * All imported content is stored under /SESSION_NAME/ * The original full path is retained to facilitate browsing * The session content can be deleted when the session is over * @param sourceFile * @return vfs uri * @throws IOException */ public static Uri importContent(String sessionId, java.io.File sourceFile) throws IOException { //list("/"); String targetPath = "/" + sessionId + "/upload/" + UUID.randomUUID().toString() + "/" + sourceFile.getName().replaceAll("[^a-zA-Z0-9\\.\\-]", "_"); targetPath = createUniqueFilename(targetPath); copyToVfs( sourceFile, targetPath ); //list("/"); return vfsUri(targetPath); } /** * Copy device content into vfs. * All imported content is stored under /SESSION_NAME/ * The original full path is retained to facilitate browsing * The session content can be deleted when the session is over * @param sessionId * @return vfs uri * @throws IOException */ public static Uri importContent(String sessionId, String fileName, InputStream sourceStream) throws IOException { //list("/"); String targetPath = "/" + sessionId + "/upload/" + UUID.randomUUID().toString() + '/' + fileName.replaceAll("[^a-zA-Z0-9\\.\\-]", "_"); targetPath = createUniqueFilename(targetPath); copyToVfs( sourceStream, targetPath ); //list("/"); return vfsUri(targetPath); } /** * Copy device content into vfs. * All imported content is stored under /SESSION_NAME/ * The original full path is retained to facilitate browsing * The session content can be deleted when the session is over * @param sessionId * @return vfs uri * @throws IOException */ public static Uri createContentPath(String sessionId, String fileName) throws IOException { //list("/"); String targetPath = "/" + sessionId + "/upload/" + UUID.randomUUID().toString() + '/' + fileName.replaceAll("[^a-zA-Z0-9\\.\\-]", "_"); targetPath = createUniqueFilename(targetPath); mkdirs( targetPath ); //list("/"); return vfsUri(targetPath); } /** * Resize an image to an efficient size for sending via OTRDATA, then copy * that resized version into vfs. All imported content is stored under * /SESSION_NAME/ The original full path is retained to facilitate browsing * The session content can be deleted when the session is over * * @param sessionId * @return vfs uri * @throws IOException */ public static Uri resizeAndImportImage(Context context, String sessionId, Uri uri, String mimeType) throws IOException { String originalImagePath = uri.getPath(); String targetPath = "/" + sessionId + "/upload/" + UUID.randomUUID().toString() + "/image"; boolean savePNG = false; if (originalImagePath.endsWith(".png") || (mimeType != null && mimeType.contains("png")) || originalImagePath.endsWith(".gif") || (mimeType != null && mimeType.contains("gif")) ) { savePNG = true; targetPath += ".png"; } else { targetPath += ".jpg"; } //load lower-res bitmap Bitmap bmp = getThumbnailFile(context, uri, DEFAULT_IMAGE_WIDTH); File file = new File(targetPath); file.getParentFile().mkdirs(); FileOutputStream out = new info.guardianproject.iocipher.FileOutputStream(file); if (savePNG) bmp.compress(Bitmap.CompressFormat.PNG, 100, out); else bmp.compress(Bitmap.CompressFormat.JPEG, 90, out); out.flush(); out.close(); bmp.recycle(); return vfsUri(targetPath); } public static InputStream openInputStream (Context context, Uri uri) throws FileNotFoundException { InputStream is; if (uri.getScheme() != null && uri.getScheme().equals("vfs")) is = new info.guardianproject.iocipher.FileInputStream(uri.getPath()); else is = context.getContentResolver().openInputStream(uri); return is; } public static Bitmap getThumbnailFile(Context context, Uri uri, int thumbnailSize) throws IOException { InputStream is = openInputStream(context, uri); BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; options.inInputShareable = true; options.inPurgeable = true; BitmapFactory.decodeStream(is, null, options); if ((options.outWidth == -1) || (options.outHeight == -1)) return null; int originalSize = (options.outHeight > options.outWidth) ? options.outHeight : options.outWidth; is.close(); is = openInputStream(context, uri); BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inSampleSize = calculateInSampleSize(options, thumbnailSize, thumbnailSize); Bitmap scaledBitmap = BitmapFactory.decodeStream(is, null, opts); is.close(); InputStream isEx = openInputStream(context,uri); ExifInterface exif = new ExifInterface(isEx); int orientationType = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); int orientationD = 0; if (orientationType == ExifInterface.ORIENTATION_ROTATE_90) orientationD = 90; else if (orientationType == ExifInterface.ORIENTATION_ROTATE_180) orientationD = 180; else if (orientationType == ExifInterface.ORIENTATION_ROTATE_270) orientationD = 270; if (orientationD != 0) scaledBitmap = rotateBitmap(scaledBitmap, orientationD); isEx.close(); return scaledBitmap; } public static void exportAll(String sessionId ) throws IOException { } public static void exportContent(String mimeType, Uri mediaUri, java.io.File exportPath) throws IOException { String sourcePath = mediaUri.getPath(); copyToExternal( sourcePath, exportPath); } public static java.io.File exportPath(String mimeType, Uri mediaUri) { java.io.File targetFilename; if (mimeType.startsWith("image")) { targetFilename = new java.io.File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),mediaUri.getLastPathSegment()); } else if (mimeType.startsWith("audio")) { targetFilename = new java.io.File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC),mediaUri.getLastPathSegment()); } else { targetFilename = new java.io.File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),mediaUri.getLastPathSegment()); } java.io.File targetUniqueFilename = createUniqueFilenameExternal(targetFilename); return targetFilename; } public static void copyToVfs(String sourcePath, String targetPath) throws IOException { copyToVfs(new java.io.File(sourcePath), targetPath); } public static void copyToVfs(java.io.File sourceFile, String targetPath) throws IOException { // create the target directories tree mkdirs( targetPath ); // copy java.io.InputStream fis = null; fis = new java.io.FileInputStream(sourceFile); FileOutputStream fos = new FileOutputStream(new File(targetPath), false); IOUtils.copyLarge(fis, fos); fos.close(); fis.close(); } public static void copyToVfs(InputStream sourceIS, String targetPath) throws IOException { // create the target directories tree mkdirs( targetPath ); // copy FileOutputStream fos = new FileOutputStream(new File(targetPath), false); IOUtils.copyLarge(sourceIS, fos); fos.close(); sourceIS.close(); } public static void copyToVfs(byte buf[], String targetPath) throws IOException { File file = new File(targetPath); FileOutputStream out = new FileOutputStream(file); out.write(buf); out.close(); } public static void copyToExternal(String sourcePath, java.io.File targetPath) throws IOException { // copy FileInputStream fis = new FileInputStream(new File(sourcePath)); java.io.FileOutputStream fos = new java.io.FileOutputStream(targetPath, false); IOUtils.copyLarge(fis, fos); fos.close(); fis.close(); } private static void mkdirs(String targetPath) throws IOException { File targetFile = new File(targetPath); if (!targetFile.exists()) { File dirFile = targetFile.getParentFile(); if (!dirFile.exists()) { boolean created = dirFile.mkdirs(); if (!created) { throw new IOException("Error creating " + targetPath); } } } } public static boolean exists(String path) { return new File(path).exists(); } public static boolean sessionExists(String sessionId) { return exists( "/" + sessionId ); } private static String createUniqueFilename( String filename ) { if (!exists(filename)) { return filename; } int count = 1; String uniqueName; File file; do { uniqueName = formatUnique(filename, count++); file = new File(uniqueName); } while(file.exists()); return uniqueName; } private static String formatUnique(String filename, int counter) { int lastDot = filename.lastIndexOf("."); if (lastDot != -1) { String name = filename.substring(0,lastDot); String ext = filename.substring(lastDot); return name + "-" + counter + "." + ext; } else { return filename + counter; } } public static String getDownloadFilename(String sessionId, String filenameFromUrl) { String filename = "/" + sessionId + "/download/" + filenameFromUrl.replaceAll("[^a-zA-Z0-9\\.\\-]", "_"); String uniqueFilename = createUniqueFilename(filename); return uniqueFilename; } private static java.io.File createUniqueFilenameExternal(java.io.File filename ) { if (!filename.exists()) { return filename; } int count = 1; String uniqueName; java.io.File file; do { uniqueName = formatUnique(filename.getName(), count++); file = new java.io.File(filename.getParentFile(),uniqueName); } while(file.exists()); return file; } public static int getImageOrientation(String imagePath) { int rotate = 0; try { ExifInterface exif = new ExifInterface(imagePath); int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1); switch (orientation) { case ExifInterface.ORIENTATION_ROTATE_270: rotate = 270; break; case ExifInterface.ORIENTATION_ROTATE_180: rotate = 180; break; case ExifInterface.ORIENTATION_ROTATE_90: rotate = 90; break; } } catch (IOException e) { e.printStackTrace(); } return rotate; } public static Bitmap rotateBitmap(Bitmap bitmap, int rotate) { Matrix matrix = new Matrix(); matrix.postRotate(rotate); Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); return rotatedBitmap; } public static int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { inSampleSize *= 2; } } return inSampleSize; } }