1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.messaging.util; 18 19 import android.content.ContentResolver; 20 import android.content.Context; 21 import android.net.Uri; 22 import android.os.Environment; 23 import android.text.TextUtils; 24 import android.webkit.MimeTypeMap; 25 26 import com.android.messaging.Factory; 27 import com.android.messaging.R; 28 import com.google.common.io.Files; 29 30 import java.io.File; 31 import java.io.IOException; 32 import java.text.SimpleDateFormat; 33 import java.util.Date; 34 import java.util.Locale; 35 36 public class FileUtil { 37 /** Returns a new file name, ensuring that such a file does not already exist. */ getNewFile(File directory, String extension, String fileNameFormat)38 private static synchronized File getNewFile(File directory, String extension, 39 String fileNameFormat) throws IOException { 40 final Date date = new Date(System.currentTimeMillis()); 41 final SimpleDateFormat dateFormat = new SimpleDateFormat(fileNameFormat); 42 final String numberedFileNameFormat = dateFormat.format(date) + "_%02d" + "." + extension; 43 for (int i = 1; i <= 99; i++) { // Only save 99 of the same file name. 44 final String newName = String.format(Locale.US, numberedFileNameFormat, i); 45 File testFile = new File(directory, newName); 46 if (!testFile.exists()) { 47 testFile.createNewFile(); 48 return testFile; 49 } 50 } 51 LogUtil.e(LogUtil.BUGLE_TAG, "Too many duplicate file names: " + numberedFileNameFormat); 52 return null; 53 } 54 55 /** 56 * Creates an unused name to use for creating a new file. The format happens to be similar 57 * to that used by the Android camera application. 58 * 59 * @param directory directory that the file should be saved to 60 * @param contentType of the media being saved 61 * @return file name to be used for creating the new file. The caller is responsible for 62 * actually creating the file. 63 */ getNewFile(File directory, String contentType)64 public static File getNewFile(File directory, String contentType) throws IOException { 65 MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); 66 String fileExtension = mimeTypeMap.getExtensionFromMimeType(contentType); 67 68 final Context context = Factory.get().getApplicationContext(); 69 String fileNameFormat = context.getString(ContentType.isImageType(contentType) 70 ? R.string.new_image_file_name_format : R.string.new_file_name_format); 71 return getNewFile(directory, fileExtension, fileNameFormat); 72 } 73 74 /** Delete everything below and including root */ removeFileOrDirectory(File root)75 public static void removeFileOrDirectory(File root) { 76 removeFileOrDirectoryExcept(root, null); 77 } 78 79 /** Delete everything below and including root except for the given file */ removeFileOrDirectoryExcept(File root, File exclude)80 public static void removeFileOrDirectoryExcept(File root, File exclude) { 81 if (root.exists()) { 82 if (root.isDirectory()) { 83 for (File file : root.listFiles()) { 84 if (exclude == null || !file.equals(exclude)) { 85 removeFileOrDirectoryExcept(file, exclude); 86 } 87 } 88 root.delete(); 89 } else if (root.isFile()) { 90 root.delete(); 91 } 92 } 93 } 94 95 /** 96 * Move all files and folders under a directory into the target. 97 */ moveAllContentUnderDirectory(File sourceDir, File targetDir)98 public static void moveAllContentUnderDirectory(File sourceDir, File targetDir) { 99 if (sourceDir.isDirectory() && targetDir.isDirectory()) { 100 if (isSameOrSubDirectory(sourceDir, targetDir)) { 101 LogUtil.e(LogUtil.BUGLE_TAG, "Can't move directory content since the source " + 102 "directory is a parent of the target"); 103 return; 104 } 105 for (File file : sourceDir.listFiles()) { 106 if (file.isDirectory()) { 107 final File dirTarget = new File(targetDir, file.getName()); 108 dirTarget.mkdirs(); 109 moveAllContentUnderDirectory(file, dirTarget); 110 } else { 111 try { 112 final File fileTarget = new File(targetDir, file.getName()); 113 Files.move(file, fileTarget); 114 } catch (IOException e) { 115 LogUtil.e(LogUtil.BUGLE_TAG, "Failed to move files", e); 116 // Try proceed with the next file. 117 } 118 } 119 } 120 } 121 } 122 isFileUri(final Uri uri)123 private static boolean isFileUri(final Uri uri) { 124 return TextUtils.equals(uri.getScheme(), ContentResolver.SCHEME_FILE); 125 } 126 127 // Checks if the file is in /data, and don't allow any app to send personal information. 128 // We're told it's possible to create world readable hardlinks to other apps private data 129 // so we ban all /data file uris. isInPrivateDir(Uri uri)130 public static boolean isInPrivateDir(Uri uri) { 131 if (!isFileUri(uri)) { 132 return false; 133 } 134 final File file = new File(uri.getPath()); 135 return FileUtil.isSameOrSubDirectory(Environment.getDataDirectory(), file); 136 } 137 138 /** 139 * Checks, whether the child directory is the same as, or a sub-directory of the base 140 * directory. 141 */ isSameOrSubDirectory(File base, File child)142 private static boolean isSameOrSubDirectory(File base, File child) { 143 try { 144 base = base.getCanonicalFile(); 145 child = child.getCanonicalFile(); 146 File parentFile = child; 147 while (parentFile != null) { 148 if (base.equals(parentFile)) { 149 return true; 150 } 151 parentFile = parentFile.getParentFile(); 152 } 153 return false; 154 } catch (IOException ex) { 155 LogUtil.e(LogUtil.BUGLE_TAG, "Error while accessing file", ex); 156 return false; 157 } 158 } 159 } 160