package me.shouheng.omnilist.utils; import android.annotation.SuppressLint; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.os.RemoteException; import android.os.StatFs; import android.provider.DocumentsContract; import android.provider.MediaStore; import android.provider.OpenableColumns; import android.support.annotation.DrawableRes; import android.support.v4.content.FileProvider; import android.text.TextUtils; import android.webkit.MimeTypeMap; import org.apache.commons.io.FileUtils; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.InvalidParameterException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Locale; import me.shouheng.omnilist.BuildConfig; import me.shouheng.omnilist.PalmApp; import me.shouheng.omnilist.R; import me.shouheng.omnilist.config.Constants; import me.shouheng.omnilist.manager.MediaStoreFactory; import me.shouheng.omnilist.model.Attachment; import me.shouheng.omnilist.model.tools.ModelFactory; import me.shouheng.omnilist.provider.PalmDB; import static java.lang.Long.parseLong; /** * Created by wangshouheng on 2017/4/7.*/ public class FileHelper { private final static String EXTERNAL_STORAGE_FOLDER = "Omni List"; private final static String HTML_EXPORT_DIR_NAME = "Exported Html"; private final static String TEXT_EXPORT_DIR_NAME = "Exported Text"; private final static String EXTERNAL_STORAGE_BACKUP_DIR = "Backup"; private final static String DATE_FORMAT_SORTABLE = "yyyyMMdd_HHmmss_SSS"; private static final String ANSI_INVALID_CHARACTERS = "\\/:*?\"<>|"; private static boolean isStorageWritable() { boolean isExternalStorageAvailable; boolean isExternalStorageWritable; String state = Environment.getExternalStorageState(); switch (state) { case Environment.MEDIA_MOUNTED: isExternalStorageAvailable = true; isExternalStorageWritable = true; break; case Environment.MEDIA_MOUNTED_READ_ONLY: isExternalStorageAvailable = true; isExternalStorageWritable = false; break; default: isExternalStorageAvailable = false; isExternalStorageWritable = false; break; } return isExternalStorageAvailable && isExternalStorageWritable; } // region Name public static String getDefaultFileName(String extension) { Calendar now = Calendar.getInstance(); SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT_SORTABLE, Locale.getDefault()); String name = sdf.format(now.getTime()); name += extension != null ? extension : ""; return removeInvalidCharacters(name); } public static String getNameFromUri(Context mContext, Uri uri) { String fileName = ""; Cursor cursor = null; try { cursor = mContext.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null); if (cursor != null) { try { if (cursor.moveToFirst()) { fileName = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); } } catch (Exception e) { LogUtils.e("Error managing diskk cache", e); } } else { fileName = uri.getLastPathSegment(); } } catch (SecurityException e) { return null; } finally { if (cursor != null) { cursor.close(); } } return fileName; } public static String getFileName(File file) { return getFileName(file.getPath()); } public static String getFileName(String path) { String name = path.substring(path.lastIndexOf(System.getProperty("file.separator")) + 1, path.length()); return removeInvalidCharacters(name); } private static String removeInvalidCharacters(final String fileName) { String fixedUpString = Uri.decode(fileName); for (int i = 0; i < ANSI_INVALID_CHARACTERS.length(); i++) { fixedUpString = fixedUpString.replace(ANSI_INVALID_CHARACTERS.charAt(i), '_'); } return Uri.encode(fixedUpString); } // endregion // region Path @SuppressLint("NewApi") public static String getPath(final Context context, final Uri uri) { if (uri == null) return null; final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { if (isExternalStorageDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; if ("primary".equalsIgnoreCase(type)) { return Environment.getExternalStorageDirectory() + "/" + split[1]; } } else if (isDownloadsDocument(uri)) { final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), parseLong(DocumentsContract.getDocumentId(uri))); return getDataColumn(context, contentUri, null, null); } else if (isMediaDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; Uri contentUri = MediaStoreFactory.getInstance().createURI(type); final String selection = "_id=?"; final String[] selectionArgs = new String[]{split[1]}; return getDataColumn(context, contentUri, selection, selectionArgs); } } else if ("content".equalsIgnoreCase(uri.getScheme())) { return getDataColumn(context, uri, null, null); } else if ("file".equalsIgnoreCase(uri.getScheme())) { return uri.getPath(); } return null; } static boolean isMediaDocument(Uri uri) { return "com.android.providers.media.documents".equals(uri.getAuthority()); } static boolean isExternalStorageDocument(Uri uri) { return "com.android.externalstorage.documents".equals(uri.getAuthority()); } static boolean isDownloadsDocument(Uri uri) { return "com.android.providers.downloads.documents".equals(uri.getAuthority()); } private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { Cursor cursor = null; final String column = "_data"; final String[] projection = {column}; try { cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); if (cursor != null && cursor.moveToFirst()) { final int column_index = cursor.getColumnIndexOrThrow(column); return cursor.getString(column_index); } } catch (Exception e) { LogUtils.e("Error retrieving uri path", e); } finally { if (cursor != null) { cursor.close(); } } return null; } // endregion // region Size public static long getSize(File directory) { StatFs statFs = new StatFs(directory.getAbsolutePath()); long blockSize = 0; try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { blockSize = statFs.getBlockSizeLong(); } else { blockSize = statFs.getBlockSize(); } // Can't understand why on some devices this fails } catch (NoSuchMethodError e) { LogUtils.e("Mysterious error", e); } return getSize(directory, blockSize); } private static long getSize(File directory, long blockSize) { if (blockSize == 0) { throw new InvalidParameterException("Blocksize can't be 0"); } File[] files = directory.listFiles(); if (files != null) { // space used by directory itself long size = directory.length(); for (File file : files) { if (file.isDirectory()) { // space used by subdirectory size += getSize(file, blockSize); } else { // file size need to rounded up to full block sizes // (not a perfect function, it adds additional block to 0 sized files // and file who perfectly fill their blocks) size += (file.length() / blockSize + 1) * blockSize; } } return size; } else { return 0; } } public static byte[] getFileBytes(File file) throws IOException, RemoteException { return getFileBytes(file, 0, (int) file.length()); } private static byte[] getFileBytes(File file, final int offset, final int size) throws IOException, RemoteException { final FileInputStream fis = new FileInputStream(file); final ByteArrayOutputStream memorySteam = new ByteArrayOutputStream(size); int ret = copyStreamContents(offset, size, fis, memorySteam); return memorySteam.toByteArray(); } private static int copyStreamContents(final long offset, final int size, final InputStream input, final OutputStream output) throws IOException { byte[] buffer = new byte[size]; int count = 0, n; final long skipAmount = input.skip(offset); if (skipAmount != offset) { throw new RuntimeException(String.format(Locale.getDefault(), "Unable to skip in the input stream actual %d, expected %d", skipAmount, offset)); } while (-1 != (n = input.read(buffer))) { output.write(buffer, 0, n); count += n; } return count; } // endregion // region MimeType /** * Get mime type of given uri. * * @param mContext the context * @param uri the uri belows to the file stored in the app file system, so the mime type got * is the mime type of file in file system. * @return the mime type */ public static String getMimeType(Context mContext, Uri uri) { ContentResolver resolver = mContext.getContentResolver(); String mimeType = resolver.getType(uri); if (TextUtils.isEmpty(mimeType)) mimeType = getMimeType(uri.toString()); return mimeType; } private static String getMimeType(String url) { String type = null; String extension = MimeTypeMap.getFileExtensionFromUrl(url); if (extension != null) { MimeTypeMap mime = MimeTypeMap.getSingleton(); type = mime.getMimeTypeFromExtension(extension); } return type; } /** * Get file mime type and translate it into the mime type of this application. * * @param mContext the context * @param uri the uri of attachment * @return the mime type of this application */ public static String getMimeTypeInternal(Context mContext, Uri uri) { String mimeType = getMimeType(mContext, uri); mimeType = getMimeTypeInternal(mimeType); return mimeType; } private static String getMimeTypeInternal(String mimeType) { if (mimeType != null) { if (mimeType.contains("image/")) { mimeType = Constants.MIME_TYPE_IMAGE; } else if (mimeType.contains("audio/")) { mimeType = Constants.MIME_TYPE_AUDIO; } else if (mimeType.contains("video/")) { mimeType = Constants.MIME_TYPE_VIDEO; } else { mimeType = Constants.MIME_TYPE_FILES; } } return mimeType; } // endregion // region extension /** * Get file extension from file name. * * @param fileName file name * @return the extension */ private static String getFileExtension(String fileName) { if (TextUtils.isEmpty(fileName)) return ""; String extension = ""; int index = fileName.lastIndexOf("."); if (index != -1) extension = fileName.substring(index, fileName.length()); return extension; } /** * Get file extension from uri. * * @param mContext the context * @param uri the uri * @return the extension */ public static String getFileExtension(Context mContext, Uri uri) { String extension; if (TextUtils.isEmpty(extension = MimeTypeMap.getFileExtensionFromUrl(uri.toString())) && TextUtils.isEmpty(extension = getFileExtension(getPath(mContext, uri)))) { String mimeType = getMimeType(mContext, uri); if (!TextUtils.isEmpty(mimeType)) { String subtype = mimeType.split("/")[1]; LogUtils.d(mimeType); return "." + subtype; } else { return ""; } } else { return extension; } } // endregion // region thumbnail public static Uri getThumbnailUri(Context mContext, Uri uri) { String mimeType = getMimeType(uri.toString()); if (!TextUtils.isEmpty(mimeType)) { String type = mimeType.split("/")[0]; String subtype = mimeType.split("/")[1]; LogUtils.d(mimeType); switch (type) { case "image": case "video": break; case "audio": uri = Uri.parse("android.resource://" + mContext.getPackageName() + "/" + R.raw.play); break; default: if ("x-vcard".equals(subtype)) { return Uri.parse("android.resource://" + mContext.getPackageName() + "/" + R.raw.vcard); } else { return getThumbnailUri(mContext, uri, subtype); } } } else { String extension, path; extension = TextUtils.isEmpty(path = getPath(mContext, uri)) ? getFileExtension(uri.toString()) : getFileExtension(path); return getThumbnailUri(mContext, uri, extension); } return uri; } private static Uri getThumbnailUri(Context mContext, Uri uri, String extension) { switch (extension) { case "mp3":case "wav":case "aac":case "wma":// audio uri = Uri.parse("android.resource://" + mContext.getPackageName() + "/" + R.raw.play); break; case "avi":case "mov":case "wmv":case "3gp":case "rmvb":case "flv":case "mpeg":case "mp4": return uri; default: uri = Uri.parse("android.resource://" + mContext.getPackageName() + "/" + R.raw.files); break; } return uri; } // endregion public static File createNewAttachmentFile(Context context, String extension) { File file = null; if (isStorageWritable()) { file = new File(context.getExternalFilesDir(null), getDefaultFileName(extension)); } return file; } public static Attachment createAttachmentFromUri(Context mContext, Uri uri) { return createAttachmentFromUri(mContext, uri, false); } private static Attachment createAttachmentFromUri(Context mContext, Uri uri, boolean moveSource) { String name = FileHelper.getNameFromUri(mContext, uri); String extension = FileHelper.getFileExtension(name).toLowerCase(Locale.getDefault()); /* The name got from last step is the {@link OpenableColumns.DISPLAY_NAME} value. * That means, for a mp3 file "Music.mp3", we may only get the "Music", so the extension can be empty. * To avoid the extension empty, we should check it and try to get it from the mime type. */ if (TextUtils.isEmpty(extension)) extension = getFileExtension(mContext, uri); File file; if (moveSource) { file = createNewAttachmentFile(mContext, extension); try { moveFile(new File(uri.getPath()), file); } catch (IOException e) { LogUtils.e("Can't move file " + uri.getPath()); } } else { file = FileHelper.createExternalStoragePrivateFile(mContext, uri, extension); } /* Create attachment object as return value. */ Attachment mAttachment = ModelFactory.getAttachment(); if (file != null) { mAttachment.setUri(getUriFromFile(mContext, file)); mAttachment.setMineType(getMimeTypeInternal(mContext, uri)); mAttachment.setName(name); mAttachment.setPath(file.getPath()); } return mAttachment; } private static File createExternalStoragePrivateFile(Context mContext, Uri uri, String extension) { if (!isStorageWritable()) { ToastUtils.makeToast(R.string.storage_not_available); return null; } File file = createNewAttachmentFile(mContext, extension); InputStream is; OutputStream os; try { is = mContext.getContentResolver().openInputStream(uri); os = new FileOutputStream(file); copyFile(is, os); } catch (IOException e) { try { is = new FileInputStream(FileHelper.getPath(mContext, uri)); os = new FileOutputStream(file); copyFile(is, os); } catch (NullPointerException e1) { try { is = new FileInputStream(uri.getPath()); os = new FileOutputStream(file); copyFile(is, os); } catch (FileNotFoundException e2) { LogUtils.e("Error writing " + file, e2); file = null; } } catch (FileNotFoundException e2) { LogUtils.e("Error writing " + file, e2); file = null; } } return file; } // region file operations public static void moveFile(File srcFile, File destFile) throws IOException { if (srcFile == null) { throw new NullPointerException("Source must not be null"); } if (destFile == null) { throw new NullPointerException("Destination must not be null"); } if (!srcFile.exists()) { throw new FileNotFoundException("Source '" + srcFile + "' does not exist"); } if (srcFile.isDirectory()) { throw new IOException("Source '" + srcFile + "' is a directory"); } if (destFile.exists()) { throw new IOException("Destination '" + destFile + "' already exists"); } if (destFile.isDirectory()) { throw new IOException("Destination '" + destFile + "' is a directory"); } boolean rename = srcFile.renameTo(destFile); if (!rename) { copyFile(srcFile, destFile ); if (!srcFile.delete()) { FileUtils.deleteQuietly(destFile); throw new IOException("Failed to delete original file '" + srcFile + "' after copy to '" + destFile + "'"); } } } public static boolean delete(Context mContext, String name) { boolean res = false; if (!isStorageWritable()) { ToastUtils.makeToast(R.string.storage_not_available); return false; } File file = new File(name); if (file.isFile()) { res = file.delete(); } else if (file.isDirectory()) { File[] files = file.listFiles(); for (File file2 : files) { res = delete(mContext, file2.getAbsolutePath()); } res = file.delete(); } return res; } public static boolean copyFile(File source, File destination) { try { return copyFile(new FileInputStream(source), new FileOutputStream(destination)); } catch (FileNotFoundException e) { LogUtils.e("Error copying file", e); return false; } } public static boolean copyFile(InputStream is, OutputStream os) { boolean res = false; byte[] data = new byte[1024]; int len; try { while ((len = is.read(data)) > 0) { os.write(data, 0, len); } is.close(); os.close(); res = true; } catch (IOException e) { LogUtils.e("Error copying file", e); } return res; } // endregion public static Uri getUriFromFile(Context context, File file){ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M){ return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file); } else { return Uri.fromFile(file); } } // region save image external /** * 保存图标到相册中 * * @param context 上下文 * @param bmp 图片 * @param isPng 是否是png格式的图片 * @param onSavedToGalleryListener the callback of saving event * @return 是否成功执行保存操作 */ public static boolean saveImageToGallery(Context context, Bitmap bmp, boolean isPng, OnSavedToGalleryListener onSavedToGalleryListener) { LogUtils.d("saveImageToGallery: " + bmp); if (bmp == null) return false; File appDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), context.getString(R.string.app_name)); if (!appDir.exists()) { if (!appDir.mkdir()) { return false; } } String fileName; if (isPng) { fileName = System.currentTimeMillis() + ".png"; } else { fileName = System.currentTimeMillis() + ".jpg"; } File file = new File(appDir, fileName); try { FileOutputStream fos = new FileOutputStream(file); if (isPng) { bmp.compress(Bitmap.CompressFormat.PNG, 100, fos); } else { bmp.compress(Bitmap.CompressFormat.JPEG, 100, fos); } bmp.recycle(); fos.flush(); fos.close(); } catch (FileNotFoundException e) { LogUtils.d("saveImageToGallery: FileNotFoundException"); e.printStackTrace(); return false; } catch (IOException e) { LogUtils.d("saveImageToGallery: IOException"); e.printStackTrace(); return false; } try { MediaStore.Images.Media.insertImage(context.getContentResolver(), file.getAbsolutePath(), fileName, null); } catch (FileNotFoundException e) { LogUtils.d("saveImageToGallery: FileNotFoundException MediaStore"); e.printStackTrace(); return false; } context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(appDir))); if (onSavedToGalleryListener != null) onSavedToGalleryListener.OnSavedToGallery(file); return true; } public static void saveDrawableToGallery(Context context, @DrawableRes int drawableRes, OnSavedToGalleryListener onSavedToGalleryListener) { Resources res = context.getResources(); BitmapDrawable d = (BitmapDrawable) res.getDrawable(drawableRes); Bitmap img = d.getBitmap(); FileHelper.saveImageToGallery(context, img, true, onSavedToGalleryListener); } // endregion // region export and import public static File getExternalStoragePublicDir() { String path = Environment.getExternalStorageDirectory() + File.separator + EXTERNAL_STORAGE_FOLDER + File.separator; File dir = new File(path); if (!dir.exists()) dir.mkdirs(); return dir; } public static File getHtmlExportDir() { File dir = new File(getExternalStoragePublicDir(), HTML_EXPORT_DIR_NAME); if (!dir.exists()) dir.mkdirs(); return dir; } public static File getTextExportDir() { File dir = new File(getExternalStoragePublicDir(), TEXT_EXPORT_DIR_NAME); if (!dir.exists()) dir.mkdirs(); return dir; } public static File getExternalBackupRootDir() { File backupDir = new File(getExternalStoragePublicDir(), EXTERNAL_STORAGE_BACKUP_DIR); if (!backupDir.exists()) backupDir.mkdirs(); return backupDir; } public static File getExternalBackupDir(String backupName) { File root = getExternalBackupRootDir(); File backupDir = new File(root, backupName); if (!backupDir.exists()) backupDir.mkdirs(); return backupDir; } public static File getExternalFilesBackupDir(File backupDir) { File attachmentsDir = FileHelper.getAttachmentDir(PalmApp.getContext()); return new File(backupDir, attachmentsDir.getName()); } public static File getAttachmentDir(Context mContext) { return mContext.getExternalFilesDir(null); } public static File copyToBackupDir(File backupDir, File file) { if (!isStorageWritable()) { return null; } if (!backupDir.exists()) { backupDir.mkdirs(); } File destination = new File(backupDir, file.getName()); copyFile(file, destination); return destination; } public static File getDatabaseFile(Context context) { return context.getDatabasePath(PalmDB.DATABASE_NAME); } public static File getPreferencesFile(Context mContext) { return new File(getPreferencesPath(mContext)); } private static String getPreferencesPath(Context mContext) { File appData = mContext.getFilesDir().getParentFile(); return appData + System.getProperty("file.separator") + "shared_prefs" + System.getProperty("file.separator") + getPreferencesName(mContext); } public static String getPreferencesName(Context mContext) { return mContext.getApplicationContext().getPackageName() + "_preferences.xml"; } // endregion public interface OnSavedToGalleryListener { void OnSavedToGallery(File file); } }