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